[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

Re: Adapting "client diff" to compare a WC dir against a repos dir

From: Karl Fogel <kfogel_at_red-bean.com>
Date: Tue, 02 Sep 2008 14:25:13 -0400

Julian Foad <julianfoad_at_btopenworld.com> writes:
> Does this attempt look like a sane way to determine whether a WC
> directory tree is the same as a repos directory tree?
>
> This is for the tree-conflict case where a merge wants to delete a dir,
> but first wants to know whether this WC dir is identical to the
> merge-left source dir.
>
> I'm trying again to write a comparison function,
> libsvn_client/merge.c:dirs_same_p(). This attempt uses some of the guts
> of repos_wc_diff() from libsvn_client/diff.c. Note that diff "summarize"
> would be doing much the same as this - retrieving minimal info on the
> differences - but is not implemented for repos:WC diffs. It looks like I
> can do it this way. The sheer number of access batons and callbacks and
> editors and things needed to set up this comparison is a bit daunting
> for me, but if it sounds reasonable I will plug in all the bits I can
> find and see if it works.
>
> Any comments gratefully received.
>
> - Julian
>
> p.s. I hate showing you unfinished uncompilable code, but if anybody
> says "No, do X instead" or "Yes, that's probably the best way," it will
> be worth it.

Why do some of the _file_() functions take mimetype arguments? Won't
you have to compare all the properties anyway, not just the mime type?

Oh, I see, because they're implementing svn_wc_diff_callbacks3_t APIs.
Are you intending to ignore some of the arguments?

Does "equal" in this context mean that all the versioned objects are
exactly the same as in the repository, or does it mean that not only all
the versioned objects are exactly the same as in the repository but also
there are no unversioned objects?

I'm really wondering if it isn't time for us to implement a standard
tree-checksum method, that can be used on both the repository and client
side. Something like this (very rough draft):

   /* Set @a *checksum to a checksum for the tree (dir or file) rooted
    * at @a path in @a repos. The checksum will be the same for all
    * trees that have the same contents, regardless of what repository
    * they are in or what sequence of edits led the tree to be in that
    * state.
    *
    * @note Entries in a directory are not ordered; only their names and
    * targets matter.
   */
   svn_error_t *
   svn_repos_tree_checksum(*checksum, repos, path);

Similarly, there would be a function like this:

   svn_error_t *
   svn_wc_tree_checksum(*checksum, repos, path,
                        svn_boolean_t consider_unversioned_objects);

Then we could just compare the checksums.

Thoughts?

> Attempt to detect whether the directory about to be deleted by a merge
> is equal to the directory in the repository at merge-left source, and
> do not perform this deletion if it is not.
>
> ### This doesn't even compile at the moment. For rough ideas only.
>
> * subversion/libsvn_client/merge.c
> (dirs_same_file_changed, etc.): New svn_wc_diff_callbacks3_t functions.
> Each will set a flag in the baton if any difference at all is indicated.
> (dirs_same_p): New function, to determine whether a WC dir is equal to a
> repos dir.
> (merge_dir_deleted): Use dirs_same_p() to decide whether to delete a dir.
>
>
> Index: subversion/libsvn_client/merge.c
> ===================================================================
> --- subversion/libsvn_client/merge.c (revision 32842)
> +++ subversion/libsvn_client/merge.c (working copy)
> @@ -1245,6 +1289,228 @@
> return SVN_NO_ERROR;
> }
>
> +/* ====================== Support for dirs_same_p ====================== */
> +
> +/* An svn_wc_diff_callbacks3_t function. */
> +static svn_error_t *
> +dirs_same_file_changed(svn_wc_adm_access_t *adm_access,
> + svn_wc_notify_state_t *contentstate,
> + svn_wc_notify_state_t *propstate,
> + const char *path,
> + const char *tmpfile1,
> + const char *tmpfile2,
> + svn_revnum_t rev1,
> + svn_revnum_t rev2,
> + const char *mimetype1,
> + const char *mimetype2,
> + const apr_array_header_t *propchanges,
> + apr_hash_t *originalprops,
> + void *diff_baton)
> +{
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +dirs_same_file_added(svn_wc_adm_access_t *adm_access,
> + svn_wc_notify_state_t *contentstate,
> + svn_wc_notify_state_t *propstate,
> + const char *path,
> + const char *tmpfile1,
> + const char *tmpfile2,
> + svn_revnum_t rev1,
> + svn_revnum_t rev2,
> + const char *mimetype1,
> + const char *mimetype2,
> + const apr_array_header_t *propchanges,
> + apr_hash_t *originalprops,
> + void *diff_baton)
> +{
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +dirs_same_file_deleted(svn_wc_adm_access_t *adm_access,
> + svn_wc_notify_state_t *state,
> + const char *path,
> + const char *tmpfile1,
> + const char *tmpfile2,
> + const char *mimetype1,
> + const char *mimetype2,
> + apr_hash_t *originalprops,
> + void *diff_baton)
> +{
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +dirs_same_dir_added(svn_wc_adm_access_t *adm_access,
> + svn_wc_notify_state_t *state,
> + const char *path,
> + svn_revnum_t rev,
> + void *diff_baton)
> +{
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +dirs_same_dir_deleted(svn_wc_adm_access_t *adm_access,
> + svn_wc_notify_state_t *state,
> + const char *path,
> + void *diff_baton)
> +{
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +dirs_same_dir_props_changed(svn_wc_adm_access_t *adm_access,
> + svn_wc_notify_state_t *propstate,
> + const char *path,
> + const apr_array_header_t *propchanges,
> + apr_hash_t *original_props,
> + void *diff_baton)
> +{
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +dirs_same_dir_opened(svn_wc_adm_access_t *adm_access,
> + const char *path,
> + svn_revnum_t rev,
> + void *diff_baton)
> +{
> + return SVN_NO_ERROR;
> +}
> +
> +static svn_error_t *
> +dirs_same_dir_closed(svn_wc_adm_access_t *adm_access,
> + svn_wc_notify_state_t *state,
> + const char *path,
> + void *diff_baton)
> +{
> + return SVN_NO_ERROR;
> +}
> +
> +
> +/* Compare the directory trees OLDER_URL_at_OLDER_REV and MINE
> + * (with its properties obtained from its WC admin area ADM_ACCESS). Set
> + * *SAME to true if they are the same or false if they differ, ignoring
> + * the "svn:mergeinfo" property, and allowing for differences due to keyword
> + * expansion and end-of-line style.
> + * Use *CTX for authentication if necessary. */
> +static svn_error_t *
> +dirs_same_p(svn_boolean_t *same,
> + const char *older_url,
> + svn_revnum_t older_rev,
> + svn_ra_session_t *ra_session,
> + const char *mine,
> + svn_wc_adm_access_t *adm_access,
> + svn_client_ctx_t *ctx,
> + apr_pool_t *pool)
> +{
> + /* Approach 1: */
> + /* Do a WC-to-repos diff. (The following is adapted from
> + * libsvn_client/diff.c:diff_repos_wc().) */
> + {
> + const char *url1, *path2, *anchor, *anchor_url, *target;
> + const svn_wc_entry_t *entry;
> + svn_ra_session_t *ra_session2;
> + const svn_wc_diff_callbacks3_t *callbacks =
> + { dirs_same_file_changed, dirs_same_file_added, dirs_same_file_deleted,
> + dirs_same_dir_added, dirs_same_dir_deleted,
> + dirs_same_dir_props_changed, dirs_same_dir_opened, dirs_same_dir_closed
> + };
> + void *callback_baton;
> + const svn_delta_editor_t *diff_editor;
> + void *diff_edit_baton;
> + const svn_ra_reporter3_t *reporter;
> + void *report_baton;
> + svn_boolean_t server_supports_depth;
> +
> + url1 = older_url;
> + path2 = mine;
> +
> + anchor = svn_wc_adm_access_path(adm_access);
> +
> + /* Fetch the URL of the anchor directory. */
> + SVN_ERR(svn_wc__entry_versioned(&entry, anchor, adm_access, FALSE, pool));
> + if (! entry->url)
> + return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
> + _("Directory '%s' has no URL"),
> + svn_path_local_style(anchor, pool));
> + anchor_url = apr_pstrdup(pool, entry->url);
> +
> + /* Establish RA session to path2's anchor */
> + SVN_ERR(svn_client__open_ra_session_internal(&ra_session2, anchor_url,
> + NULL, NULL, NULL, FALSE, TRUE,
> + ctx, pool));
> +
> + callback_baton = NULL;
> + SVN_ERR(svn_wc_get_diff_editor5(adm_access, target,
> + callbacks, callback_baton,
> + svn_depth_infinity,
> + TRUE /* ignore_ancestry */,
> + FALSE /* use_text_base */,
> + FALSE /* reverse */,
> + ctx->cancel_func, ctx->cancel_baton,
> + NULL /* changelists */,
> + &diff_editor, &diff_edit_baton, pool));
> +
> + SVN_ERR(svn_ra_do_diff3(ra_session2, &reporter, &report_baton, older_rev,
> + target ? svn_path_uri_decode(target, pool) : NULL,
> + svn_depth_infinity, TRUE /* ignore_ancestry */,
> + TRUE /* text_deltas */,
> + url1, diff_editor, diff_edit_baton, pool));
> +
> + SVN_ERR(svn_ra_has_capability(ra_session2, &server_supports_depth,
> + SVN_RA_CAPABILITY_DEPTH, pool));
> +
> + /* Create a txn mirror of path2; the diff editor will print
> + diffs in reverse. :-) */
> + SVN_ERR(svn_wc_crawl_revisions3(path2, dir_access, reporter, report_baton,
> + FALSE, svn_depth_infinity, (! server_supports_depth),
> + FALSE, NULL, NULL, /* notification is N/A */
> + NULL, pool));
> + }
> +
> + /* Approach 2: */
> + /* Read this directory from repos and compare it with WC. */
> +#if 0
> +
> + apr_hash_t *older_dirents;
> + apr_array_header_t *older_props;
> + apr_hash_t *working_props;
> +
> + /* Read directory from repos into older_dirents and older_props */
> + {
> + const char *session_url;
> + const char *rel_url;
> + apr_hash_t *prop_hash;
> + apr_array_header_t *prop_array;
> +
> + SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
> + rel_url = svn_path_is_child(session_url, older_url, pool);
> + SVN_ERR_ASSERT(rel_url);
> +
> + SVN_ERR(svn_ra_get_dir2(ra_session, &older_dirents, NULL, &prop_hash,
> + rel_url, older_rev, SVN_DIRENT_ALL, pool));
> + /* Purge non-regular props such as entry-props. */
> + prop_array = svn_prop_hash_to_array(prop_hash, pool);
> + SVN_ERR(svn_categorize_props(prop_array, NULL, NULL, &prop_array, pool));
> + }
> +
> + /* Compare the props with WC's props */
> + SVN_ERR(svn_wc_prop_list(&working_props, mine, adm_access, pool));
> + SVN_ERR(properties_same_p(same, older_props, working_props, pool));
> +
> + /* ### TODO: Compare dir entries with WC's dir entries */
> +
> + /* ### TODO: Recurse */
> +
> +#endif
> +
> + return SVN_NO_ERROR;
> +}
> +
> /* An svn_wc_diff_callbacks3_t function. */
> static svn_error_t *
> merge_dir_deleted(svn_wc_adm_access_t *adm_access,
> @@ -1255,9 +1521,6 @@
> merge_cmd_baton_t *merge_b = baton;
> apr_pool_t *subpool = svn_pool_create(merge_b->pool);
> svn_node_kind_t kind;
> - svn_wc_adm_access_t *parent_access;
> - const char *parent_path;
> - svn_error_t *err;
>
> /* Easy out: if we have no adm_access for the parent directory,
> then this portion of the tree-delta "patch" must be inapplicable.
> @@ -1276,24 +1539,40 @@
> {
> case svn_node_dir:
> {
> - svn_path_split(path, &parent_path, NULL, subpool);
> - SVN_ERR(svn_wc_adm_retrieve(&parent_access, adm_access, parent_path,
> - subpool));
> - /* Passing NULL for the notify_func and notify_baton because
> - repos_diff.c:delete_entry() will do it for us. */
> - err = svn_client__wc_delete(path, parent_access, merge_b->force,
> - merge_b->dry_run, FALSE,
> - NULL, NULL,
> - merge_b->ctx, subpool);
> - if (err)
> + /* If the dirs are identical, attempt deletion */
> +
> + svn_boolean_t same;
> + const char *child = svn_path_is_child(merge_b->target, path, subpool);
> + const char *url1;
> +
> + url1 = svn_path_url_add_component(merge_b->merge_source.url1, child,
> + subpool);
> +
> + SVN_ERR(dirs_same_p(&same, url1, merge_b->merge_source.rev1,
> + merge_b->ra_session1,
> + path, adm_access, merge_b->ctx, subpool));
> + if (same || merge_b->force)
> {
> + svn_wc_adm_access_t *parent_access;
> + const char *parent_path;
> +
> + svn_path_split(path, &parent_path, NULL, subpool);
> + SVN_ERR(svn_wc_adm_retrieve(&parent_access, adm_access,
> + parent_path, subpool));
> + /* Passing NULL for the notify_func and notify_baton because
> + repos_diff.c:delete_entry() will do it for us. */
> + SVN_ERR(svn_client__wc_delete(path, parent_access, merge_b->force,
> + merge_b->dry_run, FALSE,
> + NULL, NULL,
> + merge_b->ctx, subpool));
> if (state)
> - *state = svn_wc_notify_state_obstructed;
> - svn_error_clear(err);
> + *state = svn_wc_notify_state_changed;
> }
> - else if (state)
> + else
> {
> - *state = svn_wc_notify_state_changed;
> + /* The dirs differ, so skip instead of deleting */
> + if (state)
> + *state = svn_wc_notify_state_obstructed;
> }
> }
> break;
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe_at_subversion.tigris.org
> For additional commands, e-mail: dev-help_at_subversion.tigris.org

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe_at_subversion.tigris.org
For additional commands, e-mail: dev-help_at_subversion.tigris.org
Received on 2008-09-02 20:25:31 CEST

This is an archived mail posted to the Subversion Dev mailing list.

This site is subject to the Apache Privacy Policy and the Apache Public Forum Archive Policy.