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@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;