Copied: subversion/branches/move-tracking-2/subversion/svnmover/merge3.c (from r1710534, subversion/branches/move-tracking-2/subversion/svnmover/svnmover.c) URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/svnmover/merge3.c?p2=subversion/branches/move-tracking-2/subversion/svnmover/merge3.c&p1=subversion/branches/move-tracking-2/subversion/svnmover/svnmover.c&r1=1710534&r2=1710565&rev=1710565&view=diff ============================================================================== --- subversion/branches/move-tracking-2/subversion/svnmover/svnmover.c (original) +++ subversion/branches/move-tracking-2/subversion/svnmover/merge3.c Mon Oct 26 11:06:50 2015 @@ -1,5 +1,5 @@ /* - * svnmover.c: Concept Demo for Move Tracking and Branching + * merge3.c: 3-way merging * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one @@ -27,91 +27,25 @@ #include <apr_lib.h> -#include "svn_private_config.h" #include "svn_hash.h" #include "svn_iter.h" #include "svn_client.h" -#include "svn_cmdline.h" -#include "svn_config.h" #include "svn_error.h" -#include "svn_path.h" #include "svn_pools.h" #include "svn_props.h" #include "svn_string.h" -#include "svn_subst.h" -#include "svn_utf.h" -#include "svn_version.h" -#include "svnmover.h" -#include "private/svn_cmdline_private.h" #include "private/svn_subr_private.h" #include "private/svn_branch_repos.h" #include "private/svn_branch_nested.h" #include "private/svn_branch_compat.h" -#include "private/svn_ra_private.h" -#include "private/svn_string_private.h" #include "private/svn_sorts_private.h" -#include "private/svn_token.h" #include "private/svn_client_private.h" -#include "../libsvn_delta/debug_editor.h" - -#define HAVE_LINENOISE -#ifdef HAVE_LINENOISE -#include "../libsvn_subr/linenoise/linenoise.h" -#endif - -/* Version compatibility check */ -static svn_error_t * -check_lib_versions(void) -{ - static const svn_version_checklist_t checklist[] = - { - { "svn_client", svn_client_version }, - { "svn_subr", svn_subr_version }, - { "svn_ra", svn_ra_version }, - { NULL, NULL } - }; - SVN_VERSION_DEFINE(my_version); - - return svn_ver_check_list2(&my_version, checklist, svn_ver_equal); -} - -static svn_boolean_t quiet = FALSE; -/* UI mode: whether to display output in terms of paths or elements */ -enum { UI_MODE_EIDS, UI_MODE_PATHS, UI_MODE_SERIAL }; -static int the_ui_mode = UI_MODE_EIDS; -static const svn_token_map_t ui_mode_map[] - = { {"eids", UI_MODE_EIDS}, - {"e", UI_MODE_EIDS}, - {"paths", UI_MODE_PATHS}, - {"p", UI_MODE_PATHS}, - {"serial", UI_MODE_SERIAL}, - {"s", UI_MODE_SERIAL}, - {NULL, SVN_TOKEN_UNKNOWN} }; - -#define is_branch_root_element(branch, eid) \ - (svn_branch_root_eid(branch) == (eid)) - -/* Is BRANCH1 the same branch as BRANCH2? Compare by full branch-ids; don't - require identical branch objects. */ -#define BRANCH_IS_SAME_BRANCH(branch1, branch2, scratch_pool) \ - (strcmp(svn_branch_get_id(branch1, scratch_pool), \ - svn_branch_get_id(branch2, scratch_pool)) == 0) +#include "svnmover.h" -/* Print a notification. */ -__attribute__((format(printf, 1, 2))) -static void -notify(const char *fmt, - ...) -{ - va_list ap; +#include "svn_private_config.h" - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); - printf("\n"); -} /* Print a verbose notification: in 'quiet' mode, don't print it. */ __attribute__((format(printf, 1, 2))) @@ -121,7 +55,7 @@ notify_v(const char *fmt, { va_list ap; - if (! quiet) + /*if (! quiet)*/ { va_start(ap, fmt); vprintf(fmt, ap); @@ -130,136 +64,19 @@ notify_v(const char *fmt, } } -#define SVN_CL__LOG_SEP_STRING \ - "------------------------------------------------------------------------\n" - -/* ====================================================================== */ - -/* Update the WC to revision BASE_REVISION (SVN_INVALID_REVNUM means HEAD). - * - * Requires these fields in WC: - * head_revision - * repos_root_url - * ra_session - * pool - * - * Initializes these fields in WC: - * base_revision - * base_branch_id - * base_branch - * working_branch_id - * working_branch - * editor - * - * Assumes there are no changes in the WC: throws away the existing txn - * and starts a new one. - */ -static svn_error_t * -wc_checkout(svnmover_wc_t *wc, - svn_revnum_t base_revision, - const char *base_branch_id, - apr_pool_t *scratch_pool) +/* */ +static int +sort_compare_items_by_eid(const svn_sort__item_t *a, + const svn_sort__item_t *b) { - const char *branch_info_dir = NULL; - svn_branch_compat__shim_fetch_func_t fetch_func; - void *fetch_baton; - svn_branch_txn_t *base_txn; - - /* Validate and store the new base revision number */ - if (! SVN_IS_VALID_REVNUM(base_revision)) - base_revision = wc->head_revision; - else if (base_revision > wc->head_revision) - return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, - _("No such revision %ld (HEAD is %ld)"), - base_revision, wc->head_revision); - - /* Choose whether to store branching info in a local dir or in revprops. - (For now, just to exercise the options, we choose local files for - RA-local and revprops for a remote repo.) */ - if (strncmp(wc->repos_root_url, "file://", 7) == 0) - { - const char *repos_dir; - - SVN_ERR(svn_uri_get_dirent_from_file_url(&repos_dir, wc->repos_root_url, - scratch_pool)); - branch_info_dir = svn_dirent_join(repos_dir, "branch-info", scratch_pool); - } - - /* Get a mutable transaction based on that rev. (This implementation - re-reads all the move-tracking data from the repository.) */ - SVN_ERR(svn_ra_load_branching_state(&wc->edit_txn, - &fetch_func, &fetch_baton, - wc->ra_session, branch_info_dir, - base_revision, - wc->pool, scratch_pool)); - - wc->edit_txn = svn_nested_branch_txn_create(wc->edit_txn, wc->pool); - - /* Store the WC base state */ - base_txn = svn_branch_repos_get_base_revision_root(wc->edit_txn); - wc->base = apr_pcalloc(wc->pool, sizeof(*wc->base)); - wc->base->revision = base_revision; - wc->base->branch_id = apr_pstrdup(wc->pool, base_branch_id); - wc->base->branch - = svn_branch_txn_get_branch_by_id(base_txn, wc->base->branch_id, - scratch_pool); - if (! wc->base->branch) - return svn_error_createf(SVN_ERR_BRANCHING, NULL, - "Cannot check out WC: branch %s not found in r%ld", - base_branch_id, base_revision); - - wc->working = apr_pcalloc(wc->pool, sizeof(*wc->working)); - wc->working->revision = SVN_INVALID_REVNUM; - wc->working->branch_id = wc->base->branch_id; - wc->working->branch - = svn_branch_txn_get_branch_by_id(wc->edit_txn, wc->working->branch_id, - scratch_pool); - SVN_ERR_ASSERT(wc->working->branch); + int eid_a = *(const int *)a->key; + int eid_b = *(const int *)b->key; - return SVN_NO_ERROR; + return eid_a - eid_b; } -/* Create a simulated WC, in memory. - * - * Initializes these fields in WC: - * head_revision - * repos_root_url - * ra_session - * made_changes - * ctx - * pool - * - * BASE_REVISION is the revision to work on, or SVN_INVALID_REVNUM for HEAD. - */ -static svn_error_t * -wc_create(svnmover_wc_t **wc_p, - const char *anchor_url, - svn_revnum_t base_revision, - const char *base_branch_id, - svn_client_ctx_t *ctx, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - apr_pool_t *wc_pool = svn_pool_create(result_pool); - svnmover_wc_t *wc = apr_pcalloc(wc_pool, sizeof(*wc)); - - wc->pool = wc_pool; - wc->ctx = ctx; - - SVN_ERR(svn_client_open_ra_session2(&wc->ra_session, anchor_url, - NULL /* wri_abspath */, ctx, - wc_pool, scratch_pool)); - - SVN_ERR(svn_ra_get_repos_root2(wc->ra_session, &wc->repos_root_url, - result_pool)); - SVN_ERR(svn_ra_get_latest_revnum(wc->ra_session, &wc->head_revision, - scratch_pool)); - SVN_ERR(svn_ra_reparent(wc->ra_session, wc->repos_root_url, scratch_pool)); - SVN_ERR(wc_checkout(wc, base_revision, base_branch_id, scratch_pool)); - *wc_p = wc; - return SVN_NO_ERROR; -} +/* ====================================================================== */ /* Return (left, right) pairs of element content that differ between * subtrees LEFT and RIGHT. @@ -307,1185 +124,288 @@ element_differences(apr_hash_t **diff_p, return SVN_NO_ERROR; } -/* Set *IS_CHANGED to true if EDIT_TXN differs from its base txn, else to - * false. +/* Return a string suitable for appending to a displayed element name or + * element id to indicate that it is a subbranch root element for SUBBRANCH. + * Return "" if SUBBRANCH is null. */ -static svn_error_t * -txn_is_changed(svn_branch_txn_t *edit_txn, - svn_boolean_t *is_changed, - apr_pool_t *scratch_pool) +static const char * +branch_str(svn_branch_state_t *subbranch, + apr_pool_t *result_pool) { - SVN_ITER_T(svn_branch_state_t) *bi; - svn_branch_txn_t *base_txn - = svn_branch_repos_get_base_revision_root(edit_txn); - apr_array_header_t *edit_branches - = svn_branch_txn_get_branches(edit_txn, scratch_pool); - apr_array_header_t *base_branches - = svn_branch_txn_get_branches(base_txn, scratch_pool); - - *is_changed = FALSE; - - /* If any previous branch is now missing, that's a change. */ - for (SVN_ARRAY_ITER(bi, base_branches, scratch_pool)) - { - svn_branch_state_t *base_branch = bi->val; - svn_branch_state_t *edit_branch - = svn_branch_txn_get_branch_by_id(edit_txn, base_branch->bid, - scratch_pool); - - if (! edit_branch) - { - *is_changed = TRUE; - return SVN_NO_ERROR; - } - } - - /* If any current branch is new or changed, that's a change. */ - for (SVN_ARRAY_ITER(bi, edit_branches, scratch_pool)) - { - svn_branch_state_t *edit_branch = bi->val; - svn_branch_state_t *base_branch - = svn_branch_txn_get_branch_by_id(base_txn, edit_branch->bid, - scratch_pool); - apr_hash_t *diff; - - if (! base_branch) - { - *is_changed = TRUE; - return SVN_NO_ERROR; - } + if (subbranch) + return apr_psprintf(result_pool, + " (branch %s)", + svn_branch_get_id(subbranch, result_pool)); + return ""; +} - SVN_ERR(element_differences(&diff, - svn_branch_get_element_tree(edit_branch), - svn_branch_get_element_tree(base_branch), - scratch_pool, scratch_pool)); - if (apr_hash_count(diff)) - { - *is_changed = TRUE; - return SVN_NO_ERROR; - } - } +/* Return a string suitable for appending to a displayed element name or + * element id to indicate that BRANCH:EID is a subbranch root element. + * Return "" if the element is not a subbranch root element. + */ +static const char * +subbranch_str(svn_branch_state_t *branch, + int eid, + apr_pool_t *result_pool) +{ + svn_branch_state_t *subbranch + = svn_branch_get_subbranch_at_eid(branch, eid, result_pool); - return SVN_NO_ERROR; + return branch_str(subbranch, result_pool); } -/* Replay differences between S_LEFT and S_RIGHT into EDITOR:EDIT_BRANCH. +/* Options to control how strict the merge is about detecting conflicts. * - * S_LEFT and/or S_RIGHT may be null meaning an empty set. + * The options affect cases that, depending on the user's preference, could + * either be considered a conflict or be merged to a deterministic result. * - * Non-recursive: single branch only. + * The set of options is flexible and may be extended in future. */ -static svn_error_t * -subtree_replay(svn_branch_state_t *edit_branch, - const svn_element_tree_t *s_left, - const svn_element_tree_t *s_right, - apr_pool_t *scratch_pool) +typedef struct merge_conflict_policy_t { - apr_hash_t *diff_left_right; - apr_hash_index_t *hi; - - if (! s_left) - s_left = svn_element_tree_create(NULL, 0 /*root_eid*/, scratch_pool); - if (! s_right) - s_right = svn_element_tree_create(NULL, 0 /*root_eid*/, scratch_pool); - - SVN_ERR(element_differences(&diff_left_right, - s_left, s_right, - scratch_pool, scratch_pool)); - - /* Go through the per-element differences. */ - for (hi = apr_hash_first(scratch_pool, diff_left_right); - hi; hi = apr_hash_next(hi)) - { - int eid = svn_int_hash_this_key(hi); - svn_element_content_t **e_pair = apr_hash_this_val(hi); - svn_element_content_t *e0 = e_pair[0], *e1 = e_pair[1]; - - SVN_ERR_ASSERT(!e0 - || svn_element_payload_invariants(e0->payload)); - SVN_ERR_ASSERT(!e1 - || svn_element_payload_invariants(e1->payload)); - if (e0 || e1) - { - if (e0 && e1) - { - SVN_DBG(("replay: alter e%d", eid)); - SVN_ERR(svn_branch_state_alter_one(edit_branch, eid, - e1->parent_eid, e1->name, - e1->payload, scratch_pool)); - } - else if (e0) - { - SVN_DBG(("replay: delete e%d", eid)); - SVN_ERR(svn_branch_state_delete_one(edit_branch, eid, - scratch_pool)); - } - else - { - SVN_DBG(("replay: instan. e%d", eid)); - SVN_ERR(svn_branch_state_alter_one(edit_branch, eid, - e1->parent_eid, e1->name, - e1->payload, scratch_pool)); - } - } - } - - return SVN_NO_ERROR; -} + /* Whether to merge delete-vs-delete */ + svn_boolean_t merge_double_delete; + /* Whether to merge add-vs-add (with same parent/name/payload) */ + svn_boolean_t merge_double_add; + /* Whether to merge reparent-vs-reparent (with same parent) */ + svn_boolean_t merge_double_reparent; + /* Whether to merge rename-vs-rename (with same name) */ + svn_boolean_t merge_double_rename; + /* Whether to merge modify-vs-modify (with same payload) */ + svn_boolean_t merge_double_modify; + /* Possible additional controls: */ + /* merge (parent, name, props, text) independently or as a group */ + /* merge (parent, name) independently or as a group */ + /* merge (props, text) independently or as a group */ +} merge_conflict_policy_t; -/* */ -static apr_hash_t * -get_union_of_subbranches(svn_branch_state_t *left_branch, - svn_branch_state_t *right_branch, - apr_pool_t *result_pool) +/* An element-merge conflict description. + */ +typedef struct element_merge3_conflict_t { - apr_hash_t *all_subbranches; + svn_element_content_t *yca; + svn_element_content_t *side1; + svn_element_content_t *side2; +} element_merge3_conflict_t; - svn_branch_subtree_t *s_left - = left_branch ? svn_branch_get_subtree(left_branch, - svn_branch_root_eid(left_branch), - result_pool) : NULL; - svn_branch_subtree_t *s_right - = right_branch ? svn_branch_get_subtree(right_branch, - svn_branch_root_eid(right_branch), - result_pool) : NULL; - all_subbranches - = left_branch ? apr_hash_overlay(result_pool, - s_left->subbranches, s_right->subbranches) - : s_right->subbranches; +static element_merge3_conflict_t * +element_merge3_conflict_create(svn_element_content_t *yca, + svn_element_content_t *side1, + svn_element_content_t *side2, + apr_pool_t *result_pool) +{ + element_merge3_conflict_t *c = apr_pcalloc(result_pool, sizeof(*c)); - return all_subbranches; + c->yca = yca; + c->side1 = side1; + c->side2 = side2; + return c; } -/* Replay differences between S_LEFT and S_RIGHT into EDITOR:EDIT_BRANCH. - * - * S_LEFT or S_RIGHT (but not both) may be null meaning an empty set. - * - * Recurse into subbranches. +/* A name-clash conflict description. */ -static svn_error_t * -svn_branch_replay(svn_branch_txn_t *edit_txn, - svn_branch_state_t *edit_branch, - svn_branch_state_t *left_branch, - svn_branch_state_t *right_branch, - apr_pool_t *scratch_pool) +typedef struct name_clash_conflict_t { - assert((left_branch && right_branch) - ? (svn_branch_root_eid(left_branch) == svn_branch_root_eid(right_branch)) - : (left_branch || right_branch)); - - if (right_branch) - { - /* Replay this branch */ - const svn_element_tree_t *s_left - = left_branch ? svn_branch_get_element_tree(left_branch) : NULL; - const svn_element_tree_t *s_right - = right_branch ? svn_branch_get_element_tree(right_branch) : NULL; - - SVN_ERR(subtree_replay(edit_branch, s_left, s_right, - scratch_pool)); - } - else - { - /* deleted branch LEFT */ - /* nothing to do -- it will go away because we deleted the outer-branch - element where it was attached */ - } - - /* Replay its subbranches, recursively. - (If we're deleting the current branch, we don't also need to - explicitly delete its subbranches... do we?) */ - if (right_branch) - { - apr_hash_t *all_subbranches - = get_union_of_subbranches(left_branch, right_branch, scratch_pool); - apr_hash_index_t *hi; - - for (hi = apr_hash_first(scratch_pool, all_subbranches); - hi; hi = apr_hash_next(hi)) - { - int this_eid = svn_int_hash_this_key(hi); - svn_branch_state_t *left_subbranch - = left_branch ? svn_branch_get_subbranch_at_eid( - left_branch, this_eid, scratch_pool) : NULL; - svn_branch_state_t *right_subbranch - = right_branch ? svn_branch_get_subbranch_at_eid( - right_branch, this_eid, scratch_pool) : NULL; - svn_branch_state_t *edit_subbranch = NULL; - - /* If the subbranch is to be edited or added, first look up the - corresponding edit subbranch, or, if not found, create one. */ - if (right_subbranch) - { - const char *new_branch_id - = svn_branch_id_nest(edit_branch->bid, this_eid, scratch_pool); - - SVN_ERR(svn_branch_txn_open_branch(edit_txn, &edit_subbranch, - right_subbranch->predecessor, - new_branch_id, - svn_branch_root_eid(right_subbranch), - scratch_pool, scratch_pool)); - } + int parent_eid; + const char *name; + /* All EIDs that conflict with each other: hash of (eid -> irrelevant). */ + apr_hash_t *elements; +} name_clash_conflict_t; - /* recurse */ - if (edit_subbranch) - { - SVN_ERR(svn_branch_replay(edit_txn, edit_subbranch, - left_subbranch, right_subbranch, - scratch_pool)); - } - } - } +static name_clash_conflict_t * +name_clash_conflict_create(int parent_eid, + const char *name, + apr_pool_t *result_pool) +{ + name_clash_conflict_t *c = apr_pcalloc(result_pool, sizeof(*c)); - return SVN_NO_ERROR; + c->parent_eid = parent_eid; + c->name = apr_pstrdup(result_pool, name); + c->elements = apr_hash_make(result_pool); + return c; } -/* Replay differences between LEFT_BRANCH and RIGHT_BRANCH into - * EDIT_ROOT_BRANCH. - * (Recurse into subbranches.) +/* An orphan conflict description. */ -static svn_error_t * -replay(svn_branch_txn_t *edit_txn, - svn_branch_state_t *edit_root_branch, - svn_branch_state_t *left_branch, - svn_branch_state_t *right_branch, - apr_pool_t *scratch_pool) +typedef struct orphan_conflict_t { - SVN_ERR_ASSERT(left_branch || right_branch); + svn_element_content_t *element; +} orphan_conflict_t; - SVN_ERR(svn_branch_replay(edit_txn, edit_root_branch, - left_branch, right_branch, scratch_pool)); - return SVN_NO_ERROR; -} +static orphan_conflict_t * +orphan_conflict_create(svn_element_content_t *element, + apr_pool_t *result_pool) +{ + orphan_conflict_t *c = apr_pcalloc(result_pool, sizeof(*c)); -static svn_error_t * -commit_callback(const svn_commit_info_t *commit_info, - void *baton, - apr_pool_t *pool); + c->element = element; + return c; +} -/* Baton for commit_callback(). */ -typedef struct commit_callback_baton_t +/* */ +static conflict_storage_t * +conflict_storage_create(apr_pool_t *result_pool) { - svn_branch_txn_t *edit_txn; - const char *wc_base_branch_id; - const char *wc_commit_branch_id; - - /* just-committed revision */ - svn_revnum_t revision; -} commit_callback_baton_t; - -static svn_error_t * -display_diff_of_commit(const commit_callback_baton_t *ccbb, - apr_pool_t *scratch_pool); + conflict_storage_t *c = apr_pcalloc(result_pool, sizeof(*c)); -static svn_error_t * -do_topbranch(svn_branch_state_t **new_branch_p, - svn_branch_txn_t *txn, - svn_branch_rev_bid_eid_t *from, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); + return c; +} -/* Allocate the same number of new EIDs in NEW_TXN as are already - * allocated in OLD_TXN. - */ -static svn_error_t * -allocate_eids(svn_branch_txn_t *new_txn, - const svn_branch_txn_t *old_txn, - apr_pool_t *scratch_pool) +/* */ +static const char * +brief_eid_and_name_or_nil(svn_element_content_t *e, + apr_pool_t *result_pool) { - int num_new_eids; - int i; - - SVN_ERR(svn_branch_txn_get_num_new_eids(old_txn, &num_new_eids, - scratch_pool)); - for (i = 0; i < num_new_eids; i++) - { - SVN_ERR(svn_branch_txn_new_eid(new_txn, NULL, scratch_pool)); - } + return e ? apr_psprintf(result_pool, "%d/%s", e->parent_eid, e->name) + : "<nil>"; return SVN_NO_ERROR; } -/* Commit the changes from WC into the repository. - * - * Open a new commit txn to the repo. Replay the changes from WC into it. - * - * Set WC->head_revision and *NEW_REV_P to the committed revision number. - * - * If there are no changes to commit, set *NEW_REV_P to SVN_INVALID_REVNUM - * and do not make a commit and do not change WC->head_revision. - * - * NEW_REV_P may be null if not wanted. - */ -static svn_error_t * -wc_commit(svn_revnum_t *new_rev_p, - svnmover_wc_t *wc, - apr_hash_t *revprops, - apr_pool_t *scratch_pool) +svn_error_t * +svnmover_display_conflicts(conflict_storage_t *conflict_storage, + const char *prefix, + apr_pool_t *scratch_pool) { - const char *branch_info_dir = NULL; - svn_branch_txn_t *commit_txn; - commit_callback_baton_t ccbb; - svn_boolean_t change_detected; - const char *edit_root_branch_id; - svn_branch_state_t *edit_root_branch; + apr_hash_index_t *hi; - /* If no log msg provided, use the list of commands */ - if (! svn_hash_gets(revprops, SVN_PROP_REVISION_LOG) && wc->list_of_commands) + for (hi = apr_hash_first(scratch_pool, + conflict_storage->single_element_conflicts); + hi; hi = apr_hash_next(hi)) { - /* Avoid modifying the passed-in revprops hash */ - revprops = apr_hash_copy(scratch_pool, revprops); + int eid = svn_int_hash_this_key(hi); + element_merge3_conflict_t *c = apr_hash_this_val(hi); - svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, - svn_string_create(wc->list_of_commands, scratch_pool)); + printf("%ssingle-element conflict: e%d: yca=%s, side1=%s, side2=%s\n", + prefix, eid, + brief_eid_and_name_or_nil(c->yca, scratch_pool), + brief_eid_and_name_or_nil(c->side1, scratch_pool), + brief_eid_and_name_or_nil(c->side2, scratch_pool)); } - - /* Choose whether to store branching info in a local dir or in revprops. - (For now, just to exercise the options, we choose local files for - RA-local and revprops for a remote repo.) */ - if (strncmp(wc->repos_root_url, "file://", 7) == 0) + for (hi = apr_hash_first(scratch_pool, + conflict_storage->name_clash_conflicts); + hi; hi = apr_hash_next(hi)) { - const char *repos_dir; - - SVN_ERR(svn_uri_get_dirent_from_file_url(&repos_dir, wc->repos_root_url, - scratch_pool)); - branch_info_dir = svn_dirent_join(repos_dir, "branch-info", scratch_pool); - } + /*const char *key = apr_hash_this_key(hi);*/ + name_clash_conflict_t *c = apr_hash_this_val(hi); + apr_hash_index_t *hi2; - /* Start a new editor for the commit. */ - SVN_ERR(svn_ra_get_commit_txn(wc->ra_session, - &commit_txn, - revprops, - commit_callback, &ccbb, - NULL /*lock_tokens*/, FALSE /*keep_locks*/, - branch_info_dir, - scratch_pool)); - /*SVN_ERR(svn_branch_txn__get_debug(&wc->edit_txn, wc->edit_txn, scratch_pool));*/ - - edit_root_branch_id = wc->working->branch_id; - edit_root_branch = svn_branch_txn_get_branch_by_id( - commit_txn, wc->working->branch_id, scratch_pool); - - /* We might be creating a new top-level branch in this commit. That is the - only case in which the working branch will not be found in EDIT_TXN. - (Creating any other branch can only be done inside a checkout of a - parent branch.) So, maybe create a new top-level branch. */ - if (! edit_root_branch) - { - /* Create a new top-level branch in the edited state. (It will have - an independent new top-level branch number.) */ - svn_branch_rev_bid_eid_t *from - = svn_branch_rev_bid_eid_create(wc->base->revision, wc->base->branch_id, - svn_branch_root_eid(wc->base->branch), - scratch_pool); + printf("%sname-clash conflict: peid %d, name '%s', %d elements\n", + prefix, c->parent_eid, c->name, apr_hash_count(c->elements)); + for (hi2 = apr_hash_first(scratch_pool, c->elements); + hi2; hi2 = apr_hash_next(hi2)) + { + int eid = svn_int_hash_this_key(hi2); - SVN_ERR(do_topbranch(&edit_root_branch, commit_txn, - from, scratch_pool, scratch_pool)); - edit_root_branch_id = edit_root_branch->bid; - } - /* Allocate all the new eids we'll need in this new txn */ - SVN_ERR(allocate_eids(commit_txn, wc->working->branch->txn, scratch_pool)); - SVN_ERR(replay(commit_txn, edit_root_branch, - wc->base->branch, - wc->working->branch, - scratch_pool)); - SVN_ERR(txn_is_changed(commit_txn, &change_detected, - scratch_pool)); - if (change_detected) - { - ccbb.edit_txn = commit_txn; - ccbb.wc_base_branch_id = wc->base->branch_id; - ccbb.wc_commit_branch_id = edit_root_branch_id; - - SVN_ERR(svn_branch_txn_complete(commit_txn, scratch_pool)); - SVN_ERR(display_diff_of_commit(&ccbb, scratch_pool)); - - wc->head_revision = ccbb.revision; - if (new_rev_p) - *new_rev_p = ccbb.revision; + printf("%s element %d\n", prefix, eid); + } } - else + for (hi = apr_hash_first(scratch_pool, + conflict_storage->orphan_conflicts); + hi; hi = apr_hash_next(hi)) { - SVN_ERR(svn_branch_txn_abort(commit_txn, scratch_pool)); - if (new_rev_p) - *new_rev_p = SVN_INVALID_REVNUM; - } - - wc->list_of_commands = NULL; + int eid = svn_int_hash_this_key(hi); + orphan_conflict_t *c = apr_hash_this_val(hi); + printf("%sorphan conflict: element %d/%s: peid %d does not exist\n", + prefix, eid, c->element->name, c->element->parent_eid); + } return SVN_NO_ERROR; } -typedef enum action_code_t { - ACTION_INFO_WC, - ACTION_DIFF, - ACTION_LOG, - ACTION_LIST_BRANCHES, - ACTION_LIST_BRANCHES_R, - ACTION_LS, - ACTION_TBRANCH, - ACTION_BRANCH, - ACTION_BRANCH_INTO, - ACTION_MKBRANCH, - ACTION_MERGE, - ACTION_MV, - ACTION_MKDIR, - ACTION_PUT_FILE, - ACTION_CAT, - ACTION_CP, - ACTION_RM, - ACTION_CP_RM, - ACTION_BR_RM, - ACTION_BR_INTO_RM, - ACTION_COMMIT, - ACTION_UPDATE, - ACTION_SWITCH, - ACTION_STATUS, - ACTION_REVERT, - ACTION_MIGRATE -} action_code_t; - -typedef struct action_defn_t { - enum action_code_t code; - const char *name; - int num_args; - const char *args_help; - const char *help; -} action_defn_t; - -#define NL "\n " -static const action_defn_t action_defn[] = -{ - {ACTION_INFO_WC, "info-wc", 0, "", - "print information about the WC"}, - {ACTION_LIST_BRANCHES, "branches", 1, "PATH", - "list all branches rooted at the same element as PATH"}, - {ACTION_LIST_BRANCHES_R, "ls-br-r", 0, "", - "list all branches, recursively"}, - {ACTION_LS, "ls", 1, "PATH", - "list elements in the branch found at PATH"}, - {ACTION_LOG, "log", 2, "FROM@REV TO@REV", - "show per-revision diffs between FROM and TO"}, - {ACTION_TBRANCH, "tbranch", 1, "SRC", - "branch the branch-root or branch-subtree at SRC" NL - "to make a new top-level branch"}, - {ACTION_BRANCH, "branch", 2, "SRC DST", - "branch the branch-root or branch-subtree at SRC" NL - "to make a new branch at DST"}, - {ACTION_BRANCH_INTO, "branch-into", 2, "SRC DST", - "make a branch of the existing subtree SRC appear at" NL - "DST as part of the existing branch that contains DST" NL - "(like merging the creation of SRC to DST)"}, - {ACTION_MKBRANCH, "mkbranch", 1, "ROOT", - "make a directory that's the root of a new subbranch"}, - {ACTION_DIFF, "diff", 2, "LEFT@REV RIGHT@REV", - "show differences from subtree LEFT to subtree RIGHT"}, - {ACTION_MERGE, "merge", 3, "FROM TO YCA@REV", - "3-way merge YCA->FROM into TO"}, - {ACTION_CP, "cp", 2, "REV SRC DST", - "copy SRC@REV to DST"}, - {ACTION_MV, "mv", 2, "SRC DST", - "move SRC to DST"}, - {ACTION_RM, "rm", 1, "PATH", - "delete PATH"}, - {ACTION_CP_RM, "copy-and-delete", 2, "SRC DST", - "copy-and-delete SRC to DST"}, - {ACTION_BR_RM, "branch-and-delete", 2, "SRC DST", - "branch-and-delete SRC to DST"}, - {ACTION_BR_INTO_RM, "branch-into-and-delete", 2, "SRC DST", - "merge-and-delete SRC to DST"}, - {ACTION_MKDIR, "mkdir", 1, "PATH", - "create new directory PATH"}, - {ACTION_PUT_FILE, "put", 2, "LOCAL_FILE PATH", - "add or modify file PATH with text copied from" NL - "LOCAL_FILE (use \"-\" to read from standard input)"}, - {ACTION_CAT, "cat", 1, "PATH", - "display text (for a file) and props (if any) of PATH"}, - {ACTION_COMMIT, "commit", 0, "", - "commit the changes"}, - {ACTION_UPDATE, "update", 1, ".@REV", - "update to revision REV, keeping local changes"}, - {ACTION_SWITCH, "switch", 1, "TARGET[@REV]", - "switch to another branch and/or revision, keeping local changes"}, - {ACTION_STATUS, "status", 0, "", - "same as 'diff .@base .'"}, - {ACTION_REVERT, "revert", 0, "", - "revert all uncommitted changes"}, - {ACTION_MIGRATE, "migrate", 1, ".@REV", - "migrate changes from non-move-tracking revision"}, -}; - -typedef struct action_t { - /* The original command words (const char *) by which the action was - specified */ - apr_array_header_t *action_args; - - action_code_t action; - - /* argument revisions */ - svn_opt_revision_t rev_spec[3]; - - const char *branch_id[3]; - - /* argument paths */ - const char *relpath[3]; -} action_t; - -/* ====================================================================== */ - -/* Find the deepest branch in the repository of which REVNUM:BRANCH_ID:RELPATH - * is either the root element or a normal, non-sub-branch element. - * - * RELPATH is a repository-relative path. REVNUM is a revision number, or - * SVN_INVALID_REVNUM meaning the current txn. +/* Merge the payload for one element. * - * Return the location of the element in that branch, or with - * EID=-1 if no element exists there. + * If there is no conflict, set *CONFLICT_P to FALSE and *RESULT_P to the + * merged element; otherwise set *CONFLICT_P to TRUE and *RESULT_P to NULL. + * Note that *RESULT_P can be null, indicating a deletion. * - * If BRANCH_ID is null, the default is the WC base branch when REVNUM is - * specified, and the WC working branch when REVNUM is SVN_INVALID_REVNUM. + * This handles any case where at least one of (SIDE1, SIDE2, YCA) exists. * - * Return an error if branch BRANCH_ID does not exist in r<REVNUM>; otherwise, - * the result will never be NULL, as every path is within at least the root - * branch. - */ -static svn_error_t * -find_el_rev_by_rrpath_rev(svn_branch_el_rev_id_t **el_rev_p, - svnmover_wc_t *wc, - svn_revnum_t revnum, - const char *branch_id, - const char *relpath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - if (SVN_IS_VALID_REVNUM(revnum)) - { - const svn_branch_repos_t *repos = wc->working->branch->txn->repos; - - if (! branch_id) - branch_id = wc->base->branch_id; - SVN_ERR(svn_branch_repos_find_el_rev_by_path_rev(el_rev_p, repos, - revnum, - branch_id, - relpath, - result_pool, - scratch_pool)); - } - else - { - svn_branch_state_t *branch - = branch_id ? svn_branch_txn_get_branch_by_id( - wc->working->branch->txn, branch_id, scratch_pool) - : wc->working->branch; - svn_branch_el_rev_id_t *el_rev = apr_palloc(result_pool, sizeof(*el_rev)); - - if (! branch) - return svn_error_createf(SVN_ERR_BRANCHING, NULL, - _("Branch %s not found in working state"), - branch_id); - svn_branch_find_nested_branch_element_by_relpath( - &el_rev->branch, &el_rev->eid, - branch, relpath, scratch_pool); - el_rev->rev = SVN_INVALID_REVNUM; - *el_rev_p = el_rev; - } - SVN_ERR_ASSERT(*el_rev_p); - return SVN_NO_ERROR; -} - -/* Return a string suitable for appending to a displayed element name or - * element id to indicate that it is a subbranch root element for SUBBRANCH. - * Return "" if SUBBRANCH is null. - */ -static const char * -branch_str(svn_branch_state_t *subbranch, - apr_pool_t *result_pool) -{ - if (subbranch) - return apr_psprintf(result_pool, - " (branch %s)", - svn_branch_get_id(subbranch, result_pool)); - return ""; -} - -/* Return a string suitable for appending to a displayed element name or - * element id to indicate that BRANCH:EID is a subbranch root element. - * Return "" if the element is not a subbranch root element. + * Allocate the result in RESULT_POOL and/or as pointers to the inputs. */ -static const char * -subbranch_str(svn_branch_state_t *branch, +static void +payload_merge(svn_element_payload_t **result_p, + svn_boolean_t *conflict_p, int eid, - apr_pool_t *result_pool) -{ - svn_branch_state_t *subbranch - = svn_branch_get_subbranch_at_eid(branch, eid, result_pool); - - return branch_str(subbranch, result_pool); -} - -/* */ -static const char * -subtree_subbranch_str(svn_branch_subtree_t *subtree, - const char *bid, - int eid, - apr_pool_t *result_pool) -{ - svn_branch_subtree_t *subbranch - = svn_branch_subtree_get_subbranch_at_eid(subtree, eid, result_pool); - - if (subbranch) - return apr_psprintf(result_pool, - " (branch %s)", - svn_branch_id_nest(bid, eid, result_pool)); - return ""; -} - -/* */ -static const char * -el_rev_id_to_path(svn_branch_el_rev_id_t *el_rev, - apr_pool_t *result_pool) -{ - const char *path - = svn_branch_get_rrpath_by_eid(el_rev->branch, el_rev->eid, result_pool); - - return path; -} - -/* */ -static const char * -branch_peid_name_to_path(svn_branch_state_t *to_branch, - int to_parent_eid, - const char *to_name, - apr_pool_t *result_pool) -{ - const char *path - = svn_relpath_join(svn_branch_get_rrpath_by_eid(to_branch, to_parent_eid, - result_pool), - to_name, result_pool); - - return path; -} - -/* List the elements in BRANCH, in path notation. - * - * List only the elements for which a relpath is known -- that is, elements - * whose parents exist all the way up to the branch root. - */ -static svn_error_t * -list_branch_elements(svn_branch_state_t *branch, - apr_pool_t *scratch_pool) + svn_element_payload_t *side1, + svn_element_payload_t *side2, + svn_element_payload_t *yca, + const merge_conflict_policy_t *policy, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - apr_hash_t *paths_to_eid = apr_hash_make(scratch_pool); - apr_hash_index_t *hi; - SVN_ITER_T(int) *pi; + svn_boolean_t conflict = FALSE; + svn_element_payload_t *result = NULL; - for (hi = apr_hash_first(scratch_pool, svn_branch_get_elements(branch)); - hi; hi = apr_hash_next(hi)) + if (yca && side1 && side2) { - int eid = svn_int_hash_this_key(hi); - const char *relpath = svn_branch_get_path_by_eid(branch, eid, - scratch_pool); - - if (relpath) + if (svn_element_payload_equal(side1, yca, scratch_pool)) { - svn_hash_sets(paths_to_eid, relpath, apr_pmemdup(scratch_pool, - &eid, sizeof(eid))); + result = side2; } - } - for (SVN_HASH_ITER_SORTED(pi, paths_to_eid, svn_sort_compare_items_as_paths, scratch_pool)) - { - const char *relpath = pi->key; - int eid = *pi->val; - - notify(" %-20s%s", - relpath[0] ? relpath : ".", - subbranch_str(branch, eid, scratch_pool)); - } - - return SVN_NO_ERROR; -} - -/* */ -static int -sort_compare_items_by_eid(const svn_sort__item_t *a, - const svn_sort__item_t *b) -{ - int eid_a = *(const int *)a->key; - int eid_b = *(const int *)b->key; - - return eid_a - eid_b; -} - -static const char * -peid_name(const svn_element_content_t *element, - apr_pool_t *scratch_pool) -{ - if (element->parent_eid == -1) - return apr_psprintf(scratch_pool, "%3s %-10s", "", "."); - - return apr_psprintf(scratch_pool, "%3d/%-10s", - element->parent_eid, element->name); -} - -static const char elements_by_eid_header[] - = " eid parent-eid/name\n" - " --- ----------/----"; - -/* List all elements in branch BRANCH, in element notation. - */ -static svn_error_t * -list_branch_elements_by_eid(svn_branch_state_t *branch, - apr_pool_t *scratch_pool) -{ - SVN_ITER_T(svn_element_content_t) *pi; - - notify_v("%s", elements_by_eid_header); - for (SVN_HASH_ITER_SORTED(pi, svn_branch_get_elements(branch), - sort_compare_items_by_eid, scratch_pool)) - { - int eid = *(const int *)(pi->key); - svn_element_content_t *element = pi->val; - - if (element) + else if (svn_element_payload_equal(side2, yca, scratch_pool)) { - notify(" e%-3d %21s%s", - eid, - peid_name(element, scratch_pool), - subbranch_str(branch, eid, scratch_pool)); + result = side1; } - } - - return SVN_NO_ERROR; -} - -/* */ -static const char * -branch_id_header_str(const char *prefix, - apr_pool_t *result_pool) -{ - if (the_ui_mode == UI_MODE_PATHS) - { - return apr_psprintf(result_pool, - "%sbranch-id root-path\n" - "%s--------- ---------", - prefix, prefix); - } - else - { - return apr_psprintf(result_pool, - "%sbranch-id branch-name root-eid\n" - "%s--------- ----------- --------", - prefix, prefix); - } -} - -/* Show the id and path or root-eid of BRANCH. - */ -static const char * -branch_id_str(svn_branch_state_t *branch, - apr_pool_t *result_pool) -{ - apr_pool_t *scratch_pool = result_pool; - - if (the_ui_mode == UI_MODE_PATHS) - { - return apr_psprintf(result_pool, "%-10s /%s", - svn_branch_get_id(branch, scratch_pool), - svn_branch_get_root_rrpath(branch, scratch_pool)); - } - else - { - svn_element_content_t *outer_el = NULL; - svn_branch_state_t *outer_branch; - int outer_eid; - - svn_branch_get_outer_branch_and_eid(&outer_branch, &outer_eid, - branch, scratch_pool); - - if (outer_branch) - outer_el = svn_branch_get_element(outer_branch, outer_eid); - - return apr_psprintf(result_pool, "%-10s %-12s root=e%d", - svn_branch_get_id(branch, scratch_pool), - outer_el ? outer_el->name : "/", - svn_branch_root_eid(branch)); - } -} - -/* List the branch BRANCH. - * - * If WITH_ELEMENTS is true, also list the elements in it. - */ -static svn_error_t * -list_branch(svn_branch_state_t *branch, - svn_boolean_t with_elements, - apr_pool_t *scratch_pool) -{ - notify_v(" %s", branch_id_str(branch, scratch_pool)); - - if (with_elements) - { - if (the_ui_mode == UI_MODE_PATHS) + else if (policy->merge_double_modify + && svn_element_payload_equal(side1, side2, scratch_pool)) { - SVN_ERR(list_branch_elements(branch, scratch_pool)); + SVN_DBG(("e%d double modify: ... -> { ... | ... }", + eid)); + result = side1; } else { - SVN_ERR(list_branch_elements_by_eid(branch, scratch_pool)); + /* ### Need not conflict if can merge props and text separately. */ + + SVN_DBG(("e%d conflict: payload: ... -> { ... | ... }", + eid)); + conflict = TRUE; } } - return SVN_NO_ERROR; + + *result_p = result; + *conflict_p = conflict; } -/* List all branches rooted at EID. +/* Merge the content for one element. + * + * If there is no conflict, set *CONFLICT_P to FALSE and *RESULT_P to the + * merged element; otherwise set *CONFLICT_P to TRUE and *RESULT_P to NULL. + * Note that *RESULT_P can be null, indicating a deletion. + * + * This handles any case where at least one of (SIDE1, SIDE2, YCA) exists. * - * If WITH_ELEMENTS is true, also list the elements in each branch. + * Allocate the result in RESULT_POOL and/or as pointers to the inputs. */ -static svn_error_t * -list_branches(svn_branch_txn_t *txn, +static void +element_merge(svn_element_content_t **result_p, + element_merge3_conflict_t **conflict_p, int eid, - svn_boolean_t with_elements, + svn_element_content_t *side1, + svn_element_content_t *side2, + svn_element_content_t *yca, + const merge_conflict_policy_t *policy, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - const apr_array_header_t *branches; - SVN_ITER_T(svn_branch_state_t) *bi; - svn_boolean_t printed_header = FALSE; - - notify_v("%s", branch_id_header_str(" ", scratch_pool)); - - branches = svn_branch_txn_get_branches(txn, scratch_pool); + svn_boolean_t same1 = svn_element_content_equal(yca, side1, scratch_pool); + svn_boolean_t same2 = svn_element_content_equal(yca, side2, scratch_pool); + svn_boolean_t conflict = FALSE; + svn_element_content_t *result = NULL; - for (SVN_ARRAY_ITER(bi, branches, scratch_pool)) + if (same1) { - svn_branch_state_t *branch = bi->val; - - if (svn_branch_root_eid(branch) != eid) - continue; - - SVN_ERR(list_branch(branch, with_elements, bi->iterpool)); - if (with_elements) /* separate branches by a blank line */ - printf("\n"); + result = side2; } - - for (SVN_ARRAY_ITER(bi, branches, scratch_pool)) + else if (same2) { - svn_branch_state_t *branch = bi->val; - - if (! svn_branch_get_element(branch, eid) - || svn_branch_root_eid(branch) == eid) - continue; - - if (! printed_header) - { - if (the_ui_mode == UI_MODE_PATHS) - notify_v("branches containing but not rooted at that element:"); - else - notify_v("branches containing but not rooted at e%d:", eid); - printed_header = TRUE; - } - SVN_ERR(list_branch(branch, with_elements, bi->iterpool)); - if (with_elements) /* separate branches by a blank line */ - printf("\n"); + result = side1; } - - return SVN_NO_ERROR; -} - -/* List all branches. If WITH_ELEMENTS is true, also list the elements - * in each branch. - */ -static svn_error_t * -list_all_branches(svn_branch_txn_t *txn, - svn_boolean_t with_elements, - apr_pool_t *scratch_pool) -{ - const apr_array_header_t *branches; - SVN_ITER_T(svn_branch_state_t) *bi; - - branches = svn_branch_txn_get_branches(txn, scratch_pool); - - notify_v("branches:"); - - for (SVN_ARRAY_ITER(bi, branches, scratch_pool)) + else if (yca && side1 && side2) { - svn_branch_state_t *branch = bi->val; - - SVN_ERR(list_branch(branch, with_elements, bi->iterpool)); - if (with_elements) /* separate branches by a blank line */ - printf("\n"); - } - - return SVN_NO_ERROR; -} - -/* Options to control how strict the merge is about detecting conflicts. - * - * The options affect cases that, depending on the user's preference, could - * either be considered a conflict or be merged to a deterministic result. - * - * The set of options is flexible and may be extended in future. - */ -typedef struct merge_conflict_policy_t -{ - /* Whether to merge delete-vs-delete */ - svn_boolean_t merge_double_delete; - /* Whether to merge add-vs-add (with same parent/name/payload) */ - svn_boolean_t merge_double_add; - /* Whether to merge reparent-vs-reparent (with same parent) */ - svn_boolean_t merge_double_reparent; - /* Whether to merge rename-vs-rename (with same name) */ - svn_boolean_t merge_double_rename; - /* Whether to merge modify-vs-modify (with same payload) */ - svn_boolean_t merge_double_modify; - /* Possible additional controls: */ - /* merge (parent, name, props, text) independently or as a group */ - /* merge (parent, name) independently or as a group */ - /* merge (props, text) independently or as a group */ -} merge_conflict_policy_t; - -/* An element-merge conflict description. - */ -typedef struct element_merge3_conflict_t -{ - svn_element_content_t *yca; - svn_element_content_t *side1; - svn_element_content_t *side2; -} element_merge3_conflict_t; - -static element_merge3_conflict_t * -element_merge3_conflict_create(svn_element_content_t *yca, - svn_element_content_t *side1, - svn_element_content_t *side2, - apr_pool_t *result_pool) -{ - element_merge3_conflict_t *c = apr_pcalloc(result_pool, sizeof(*c)); - - c->yca = yca; - c->side1 = side1; - c->side2 = side2; - return c; -} - -/* A name-clash conflict description. - */ -typedef struct name_clash_conflict_t -{ - int parent_eid; - const char *name; - /* All EIDs that conflict with each other: hash of (eid -> irrelevant). */ - apr_hash_t *elements; -} name_clash_conflict_t; - -static name_clash_conflict_t * -name_clash_conflict_create(int parent_eid, - const char *name, - apr_pool_t *result_pool) -{ - name_clash_conflict_t *c = apr_pcalloc(result_pool, sizeof(*c)); - - c->parent_eid = parent_eid; - c->name = apr_pstrdup(result_pool, name); - c->elements = apr_hash_make(result_pool); - return c; -} - -/* An orphan conflict description. - */ -typedef struct orphan_conflict_t -{ - svn_element_content_t *element; -} orphan_conflict_t; - -static orphan_conflict_t * -orphan_conflict_create(svn_element_content_t *element, - apr_pool_t *result_pool) -{ - orphan_conflict_t *c = apr_pcalloc(result_pool, sizeof(*c)); - - c->element = element; - return c; -} - -typedef struct conflict_storage_t -{ - /* Single-element conflicts */ - /* (eid -> element_merge3_conflict_t) */ - apr_hash_t *single_element_conflicts; - - /* Name-clash conflicts */ - /* ("%{parent_eid}d/%{name}s" -> name_clash_conflict_t) */ - apr_hash_t *name_clash_conflicts; - - /* Orphan conflicts */ - /* (eid -> orphan_conflict_t) */ - apr_hash_t *orphan_conflicts; -} conflict_storage_t; - -/* */ -static conflict_storage_t * -conflict_storage_create(apr_pool_t *result_pool) -{ - conflict_storage_t *c = apr_pcalloc(result_pool, sizeof(*c)); - - return c; -} - -/* */ -static const char * -brief_eid_and_name_or_nil(svn_element_content_t *e, - apr_pool_t *result_pool) -{ - return e ? apr_psprintf(result_pool, "%d/%s", e->parent_eid, e->name) - : "<nil>"; - - return SVN_NO_ERROR; -} - -/* */ -static svn_error_t * -display_conflicts(conflict_storage_t *conflict_storage, - const char *prefix, - apr_pool_t *scratch_pool) -{ - apr_hash_index_t *hi; - - for (hi = apr_hash_first(scratch_pool, - conflict_storage->single_element_conflicts); - hi; hi = apr_hash_next(hi)) - { - int eid = svn_int_hash_this_key(hi); - element_merge3_conflict_t *c = apr_hash_this_val(hi); - - printf("%ssingle-element conflict: e%d: yca=%s, side1=%s, side2=%s\n", - prefix, eid, - brief_eid_and_name_or_nil(c->yca, scratch_pool), - brief_eid_and_name_or_nil(c->side1, scratch_pool), - brief_eid_and_name_or_nil(c->side2, scratch_pool)); - } - for (hi = apr_hash_first(scratch_pool, - conflict_storage->name_clash_conflicts); - hi; hi = apr_hash_next(hi)) - { - /*const char *key = apr_hash_this_key(hi);*/ - name_clash_conflict_t *c = apr_hash_this_val(hi); - apr_hash_index_t *hi2; - - printf("%sname-clash conflict: peid %d, name '%s', %d elements\n", - prefix, c->parent_eid, c->name, apr_hash_count(c->elements)); - for (hi2 = apr_hash_first(scratch_pool, c->elements); - hi2; hi2 = apr_hash_next(hi2)) - { - int eid = svn_int_hash_this_key(hi2); - - printf("%s element %d\n", prefix, eid); - } - } - for (hi = apr_hash_first(scratch_pool, - conflict_storage->orphan_conflicts); - hi; hi = apr_hash_next(hi)) - { - int eid = svn_int_hash_this_key(hi); - orphan_conflict_t *c = apr_hash_this_val(hi); - - printf("%sorphan conflict: element %d/%s: peid %d does not exist\n", - prefix, eid, c->element->name, c->element->parent_eid); - } - return SVN_NO_ERROR; -} - -/* Merge the payload for one element. - * - * If there is no conflict, set *CONFLICT_P to FALSE and *RESULT_P to the - * merged element; otherwise set *CONFLICT_P to TRUE and *RESULT_P to NULL. - * Note that *RESULT_P can be null, indicating a deletion. - * - * This handles any case where at least one of (SIDE1, SIDE2, YCA) exists. - * - * Allocate the result in RESULT_POOL and/or as pointers to the inputs. - */ -static void -payload_merge(svn_element_payload_t **result_p, - svn_boolean_t *conflict_p, - int eid, - svn_element_payload_t *side1, - svn_element_payload_t *side2, - svn_element_payload_t *yca, - const merge_conflict_policy_t *policy, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_boolean_t conflict = FALSE; - svn_element_payload_t *result = NULL; - - if (yca && side1 && side2) - { - if (svn_element_payload_equal(side1, yca, scratch_pool)) - { - result = side2; - } - else if (svn_element_payload_equal(side2, yca, scratch_pool)) - { - result = side1; - } - else if (policy->merge_double_modify - && svn_element_payload_equal(side1, side2, scratch_pool)) - { - SVN_DBG(("e%d double modify: ... -> { ... | ... }", - eid)); - result = side1; - } - else - { - /* ### Need not conflict if can merge props and text separately. */ - - SVN_DBG(("e%d conflict: payload: ... -> { ... | ... }", - eid)); - conflict = TRUE; - } - } - - *result_p = result; - *conflict_p = conflict; -} - -/* Merge the content for one element. - * - * If there is no conflict, set *CONFLICT_P to FALSE and *RESULT_P to the - * merged element; otherwise set *CONFLICT_P to TRUE and *RESULT_P to NULL. - * Note that *RESULT_P can be null, indicating a deletion. - * - * This handles any case where at least one of (SIDE1, SIDE2, YCA) exists. - * - * Allocate the result in RESULT_POOL and/or as pointers to the inputs. - */ -static void -element_merge(svn_element_content_t **result_p, - element_merge3_conflict_t **conflict_p, - int eid, - svn_element_content_t *side1, - svn_element_content_t *side2, - svn_element_content_t *yca, - const merge_conflict_policy_t *policy, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_boolean_t same1 = svn_element_content_equal(yca, side1, scratch_pool); - svn_boolean_t same2 = svn_element_content_equal(yca, side2, scratch_pool); - svn_boolean_t conflict = FALSE; - svn_element_content_t *result = NULL; - - if (same1) - { - result = side2; - } - else if (same2) - { - result = side1; - } - else if (yca && side1 && side2) - { - /* All three sides are different, and all exist */ - result = apr_pmemdup(result_pool, yca, sizeof(*result)); + /* All three sides are different, and all exist */ + result = apr_pmemdup(result_pool, yca, sizeof(*result)); /* merge the parent-eid */ if (side1->parent_eid == yca->parent_eid) @@ -1518,3092 +438,464 @@ element_merge(svn_element_content_t **re else if (strcmp(side2->name, yca->name) == 0) { result->name = side1->name; - } - else if (policy->merge_double_rename - && strcmp(side1->name, side2->name) == 0) - { - SVN_DBG(("e%d double rename: %s -> { %s | %s }", - eid, yca->name, side1->name, side2->name)); - result->name = side1->name; - } - else - { - SVN_DBG(("e%d conflict: name: %s -> { %s | %s }", - eid, yca->name, side1->name, side2->name)); - conflict = TRUE; - } - - /* merge the payload */ - { - svn_boolean_t payload_conflict; - - payload_merge(&result->payload, &payload_conflict, - eid, side1->payload, side2->payload, yca->payload, - policy, result_pool, scratch_pool); - if (payload_conflict) - conflict = TRUE; - } - } - else if (! side1 && ! side2) - { - /* Double delete (as we assume at least one of YCA/SIDE1/SIDE2 exists) */ - if (policy->merge_double_delete) - { - SVN_DBG(("e%d double delete", - eid)); - result = side1; - } - else - { - SVN_DBG(("e%d conflict: delete vs. delete", - eid)); - conflict = TRUE; - } - } - else if (side1 && side2) - { - /* Double add (as we already handled the case where YCA also exists) */ - /* May be allowed for equal content of a normal element (not subbranch) */ - if (policy->merge_double_add - && !side1->payload->is_subbranch_root - && !side2->payload->is_subbranch_root - && svn_element_content_equal(side1, side2, scratch_pool)) - { - SVN_DBG(("e%d double add", - eid)); - result = side1; - } - else - { - SVN_DBG(("e%d conflict: add vs. add (%s)", - eid, - svn_element_content_equal(side1, side2, scratch_pool) - ? "same content" : "different content")); - conflict = TRUE; - } - } - else - { - /* The remaining cases must be delete vs. modify */ - SVN_DBG(("e%d conflict: delete vs. modify: %d -> { %d | %d }", - eid, !!yca, !!side1, !!side2)); - conflict = TRUE; - } - - *result_p = result; - *conflict_p - = conflict ? element_merge3_conflict_create(yca, side1, side2, - result_pool) : NULL; -} - -static svn_error_t * -branch_merge_subtree_r(svn_branch_txn_t *edit_txn, - conflict_storage_t **conflict_storage_p, - const svn_branch_el_rev_id_t *src, - const svn_branch_el_rev_id_t *tgt, - const svn_branch_el_rev_id_t *yca, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - -/* Merge the subbranch of {SRC, TGT, YCA} found at EID. - */ -static svn_error_t * -merge_subbranch(svn_branch_txn_t *edit_txn, - const svn_branch_el_rev_id_t *src, - const svn_branch_el_rev_id_t *tgt, - const svn_branch_el_rev_id_t *yca, - int eid, - apr_pool_t *scratch_pool) -{ - svn_branch_state_t *src_subbranch - = svn_branch_get_subbranch_at_eid(src->branch, eid, scratch_pool); - svn_branch_state_t *tgt_subbranch - = svn_branch_get_subbranch_at_eid(tgt->branch, eid, scratch_pool); - svn_branch_state_t *yca_subbranch - = svn_branch_get_subbranch_at_eid(yca->branch, eid, scratch_pool); - svn_branch_el_rev_id_t *subbr_src = NULL; - svn_branch_el_rev_id_t *subbr_tgt = NULL; - svn_branch_el_rev_id_t *subbr_yca = NULL; - - if (src_subbranch) - subbr_src = svn_branch_el_rev_id_create( - src_subbranch, svn_branch_root_eid(src_subbranch), - src->rev, scratch_pool); - if (tgt_subbranch) - subbr_tgt = svn_branch_el_rev_id_create( - tgt_subbranch, svn_branch_root_eid(tgt_subbranch), - tgt->rev, scratch_pool); - if (yca_subbranch) - subbr_yca = svn_branch_el_rev_id_create( - yca_subbranch, svn_branch_root_eid(yca_subbranch), - yca->rev, scratch_pool); - - if (subbr_src && subbr_tgt && subbr_yca) /* ?edit vs. ?edit */ - { - conflict_storage_t *conflict_storage; - - /* subbranch possibly changed in source => merge */ - SVN_ERR(branch_merge_subtree_r(edit_txn, - &conflict_storage, - subbr_src, subbr_tgt, subbr_yca, - scratch_pool, scratch_pool)); - /* ### store this branch's conflict_storage somewhere ... */ - } - else if (subbr_src && subbr_yca) /* ?edit vs. delete */ - { - /* ### possible conflict (edit vs. delete) */ - } - else if (subbr_tgt && subbr_yca) /* delete vs. ?edit */ - { - /* ### possible conflict (delete vs. edit) */ - } - else if (subbr_src && subbr_tgt) /* double add */ - { - /* ### conflict */ - } - else if (subbr_src) /* added on source branch */ - { - const char *new_branch_id - = svn_branch_id_nest(svn_branch_get_id(tgt->branch, scratch_pool), - eid, scratch_pool); - svn_branch_rev_bid_eid_t *from - = svn_branch_rev_bid_eid_create(src_subbranch->txn->rev, - svn_branch_get_id(src_subbranch, - scratch_pool), - svn_branch_root_eid(src_subbranch), - scratch_pool); - - SVN_ERR(svn_branch_txn_branch(edit_txn, NULL /*new_branch_id_p*/, from, - new_branch_id, scratch_pool, scratch_pool)); - } - else if (subbr_tgt) /* added on target branch */ - { - /* nothing to do */ - } - else if (subbr_yca) /* double delete */ - { - /* ### conflict? policy option? */ - } - - return SVN_NO_ERROR; -} - -/* */ -static int -sort_compare_items_by_peid_and_name(const svn_sort__item_t *a, - const svn_sort__item_t *b) -{ - svn_element_content_t *element_a = a->value; - svn_element_content_t *element_b = b->value; - - if (element_a->parent_eid != element_b->parent_eid) - return element_a->parent_eid - element_b->parent_eid; - return strcmp(element_a->name, element_b->name); -} - -/* Return all (key -> name_clash_conflict_t) name clash conflicts in BRANCH. - */ -static svn_error_t * -detect_clashes(apr_hash_t **clashes_p, - svn_branch_state_t *branch, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - apr_hash_t *clashes = apr_hash_make(result_pool); - SVN_ITER_T(svn_element_content_t) *pi; - int prev_eid = -1; - svn_element_content_t *prev_element = NULL; - - for (SVN_HASH_ITER_SORTED(pi, svn_branch_get_elements(branch), - sort_compare_items_by_peid_and_name, scratch_pool)) - { - int eid = *(const int *)(pi->key); - svn_element_content_t *element = pi->val; - - if (prev_element - && element->parent_eid == prev_element->parent_eid - && strcmp(element->name, prev_element->name) == 0) - { - const char *key = apr_psprintf(result_pool, "%d/%s", - element->parent_eid, element->name); - name_clash_conflict_t *c; - - c = svn_hash_gets(clashes, key); - if (!c) - { - c = name_clash_conflict_create( - element->parent_eid, element->name, - result_pool); - svn_hash_sets(clashes, key, c); - } - svn_int_hash_set(c->elements, eid, &c); - svn_int_hash_set(c->elements, prev_eid, &c); - } - prev_eid = eid; - prev_element = element; - } - - *clashes_p = clashes; - return SVN_NO_ERROR; -} - -/* Return all (eid -> orphan_conflict_t) orphan conflicts in BRANCH. - */ -static svn_error_t * -detect_orphans(apr_hash_t **orphans_p, - svn_branch_state_t *branch, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - apr_hash_t *orphans = apr_hash_make(result_pool); - SVN_ITER_T(svn_element_content_t) *pi; - const svn_element_tree_t *elements = svn_branch_get_element_tree(branch); - - for (SVN_HASH_ITER(pi, scratch_pool, elements->e_map)) - { - int eid = *(const int *)(pi->key); - svn_element_content_t *element = pi->val; - - if (eid != elements->root_eid - && ! svn_element_tree_get(elements, element->parent_eid)) - { - orphan_conflict_t *c; - - c = orphan_conflict_create(element, result_pool); - svn_int_hash_set(orphans, eid, c); - - notify(" orphan: %d/%s: peid %d does not exist", - eid, element->name, element->parent_eid); - } - } - - *orphans_p = orphans; - return SVN_NO_ERROR; -} - -/* Merge ... - * - * Merge any sub-branches in the same way, recursively. - */ -static svn_error_t * -branch_merge_subtree_r(svn_branch_txn_t *edit_txn, - conflict_storage_t **conflict_storage_p, - const svn_branch_el_rev_id_t *src, - const svn_branch_el_rev_id_t *tgt, - const svn_branch_el_rev_id_t *yca, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - svn_branch_subtree_t *s_src, *s_tgt, *s_yca; - apr_hash_t *diff_yca_src, *diff_yca_tgt; - apr_hash_t *e_conflicts = apr_hash_make(scratch_pool); - conflict_storage_t *conflict_storage = conflict_storage_create(result_pool); - SVN_ITER_T(svn_element_content_t *) *pi; - apr_hash_t *all_elements; - const merge_conflict_policy_t policy = { TRUE, TRUE, TRUE, TRUE, TRUE }; - apr_pool_t *iterpool = svn_pool_create(scratch_pool); - - SVN_ERR_ASSERT(src->eid == tgt->eid); - SVN_ERR_ASSERT(src->eid == yca->eid); - - SVN_DBG(("merge src: r%2ld %s e%3d", - src->rev, - svn_branch_get_id(src->branch, scratch_pool), src->eid)); - SVN_DBG(("merge tgt: r%2ld %s e%3d", - tgt->rev, - svn_branch_get_id(tgt->branch, scratch_pool), tgt->eid)); - SVN_DBG(("merge yca: r%2ld %s e%3d", - yca->rev, - svn_branch_get_id(yca->branch, scratch_pool), yca->eid)); - - notify_v("merging into branch %s", - svn_branch_get_id(tgt->branch, scratch_pool)); - /* - for (eid, diff1) in element_differences(YCA, FROM): - diff2 = element_diff(eid, YCA, TO) - if diff1 and diff2: - result := element_merge(diff1, diff2) - elif diff1: - result := diff1.right - # else no change - */ - s_src = svn_branch_get_subtree(src->branch, src->eid, scratch_pool); - s_tgt = svn_branch_get_subtree(tgt->branch, tgt->eid, scratch_pool); - s_yca = svn_branch_get_subtree(yca->branch, yca->eid, scratch_pool); - SVN_ERR(element_differences(&diff_yca_src, - s_yca->tree, s_src->tree, - scratch_pool, scratch_pool)); - /* ### We only need to query for YCA:TO differences in elements that are - different in YCA:FROM, but right now we ask for all differences. */ - SVN_ERR(element_differences(&diff_yca_tgt, - s_yca->tree, s_tgt->tree, - scratch_pool, scratch_pool)); - - all_elements = apr_hash_overlay(scratch_pool, - svn_branch_get_elements(src->branch), - svn_branch_get_elements(tgt->branch)); - all_elements = apr_hash_overlay(scratch_pool, - svn_branch_get_elements(yca->branch), - all_elements); - for (SVN_HASH_ITER_SORTED(pi, all_elements, - sort_compare_items_by_eid, scratch_pool)) - { - int eid = *(const int *)(pi->key); - svn_element_content_t **e_yca_src - = svn_int_hash_get(diff_yca_src, eid); - svn_element_content_t **e_yca_tgt - = svn_int_hash_get(diff_yca_tgt, eid); - svn_element_content_t *e_yca; - svn_element_content_t *e_src; - svn_element_content_t *e_tgt; - svn_element_content_t *result; - element_merge3_conflict_t *conflict; - - svn_pool_clear(iterpool); - - /* If an element hasn't changed in the source branch, there is - no need to do anything with it in the target branch. We could - use element_merge() for any case where at least one of (SRC, - TGT, YCA) exists, but we choose to skip it when SRC == YCA. */ - if (! e_yca_src) - { - /* Still need to merge any subbranch linked to this element. - There were no changes to the link element but that doesn't - mean there were no changes to the linked branch. */ - SVN_ERR(merge_subbranch(edit_txn, src, tgt, yca, eid, iterpool)); - - continue; - } - - e_yca = e_yca_src[0]; - e_src = e_yca_src[1]; - e_tgt = e_yca_tgt ? e_yca_tgt[1] : e_yca_src[0]; - - element_merge(&result, &conflict, - eid, e_src, e_tgt, e_yca, - &policy, - scratch_pool, scratch_pool); - - if (conflict) - { - notify_v("! e%d <conflict>", eid); - svn_int_hash_set(e_conflicts, eid, conflict); - } - else if (e_tgt && result) - { - notify_v("M/V e%d %s%s", - eid, result->name, - subbranch_str(tgt->branch, eid, iterpool)); - - SVN_ERR(svn_branch_state_alter_one(tgt->branch, eid, - result->parent_eid, result->name, - result->payload, iterpool)); - - SVN_ERR(merge_subbranch(edit_txn, src, tgt, yca, eid, iterpool)); - } - else if (e_tgt) - { - notify_v("D e%d %s%s", - eid, e_yca->name, - subbranch_str(yca->branch, eid, iterpool)); - SVN_ERR(svn_branch_state_delete_one(tgt->branch, eid, iterpool)); - - /* ### If this is a subbranch-root element being deleted, shouldn't - we see if there were any changes to be merged in the subbranch, - and raise a delete-vs-edit conflict if so? */ - } - else if (result) - { - notify_v("A e%d %s%s", - eid, result->name, - subbranch_str(src->branch, eid, iterpool)); - - /* In BRANCH, create an instance of the element EID with new content. - * - * Translated to old language, this means create a new node-copy - * copied (branched) from the source-right version of the merge - * (which is not specified here, but will need to be), - * which may be in this branch or in another branch. - */ - SVN_ERR(svn_branch_state_alter_one(tgt->branch, eid, - result->parent_eid, result->name, - result->payload, iterpool)); - - SVN_ERR(merge_subbranch(edit_txn, src, tgt, yca, eid, iterpool)); - } - } - svn_pool_destroy(iterpool); - - /* Detect clashes. - ### TODO: Detect clashes, cycles and orphans; and report full conflict - info (including the relevant incoming changes) for each - kind of conflict. If there are no conflicts, flatten the - merge result into a tree. */ - conflict_storage->single_element_conflicts = e_conflicts; - SVN_ERR(detect_clashes(&conflict_storage->name_clash_conflicts, - tgt->branch, - result_pool, scratch_pool)); - SVN_ERR(detect_orphans(&conflict_storage->orphan_conflicts, - tgt->branch, - result_pool, scratch_pool)); - - notify_v("merging into branch %s -- finished", - svn_branch_get_id(tgt->branch, scratch_pool)); - - *conflict_storage_p = conflict_storage; - return SVN_NO_ERROR; -} - -/* Merge SRC into TGT, using the common ancestor YCA. - * - * Merge the two sets of changes: YCA -> SRC and YCA -> TGT, applying - * the result to the transaction at TGT. - * - * If conflicts arise, just fail. - * - * SRC, TGT and YCA must be existing and corresponding (same EID) elements. - * - * None of SRC, TGT and YCA is a subbranch root element. - * - * Nested subbranches will also be merged. - */ -static svn_error_t * -svn_branch_merge(svn_branch_txn_t *edit_txn, - conflict_storage_t **conflict_storage_p, - svn_branch_el_rev_id_t *src, - svn_branch_el_rev_id_t *tgt, - svn_branch_el_rev_id_t *yca, - apr_pool_t *scratch_pool) -{ - /*SVN_ERR(verify_exists_in_branch(from, scratch_pool));*/ - /*SVN_ERR(verify_exists_in_branch(to, scratch_pool));*/ - /*SVN_ERR(verify_exists_in_branch(yca, scratch_pool));*/ - if (src->eid != tgt->eid || src->eid != yca->eid) - return svn_error_createf(SVN_ERR_BRANCHING, NULL, - _("Merge branches must all be same element " - "(from: e%d, to: e%d, yca: e%d)"), - src->eid, tgt->eid, yca->eid); - /*SVN_ERR(verify_not_subbranch_root(from, scratch_pool));*/ - /*SVN_ERR(verify_not_subbranch_root(to, scratch_pool));*/ - /*SVN_ERR(verify_not_subbranch_root(yca, scratch_pool));*/ - - SVN_ERR(branch_merge_subtree_r(edit_txn, - conflict_storage_p, - src, tgt, yca, - scratch_pool, scratch_pool)); - - return SVN_NO_ERROR; -} - -/* Switch the WC to revision BASE_REVISION (SVN_INVALID_REVNUM means HEAD) - * and branch BRANCH_ID. - * - * Merge any changes in the existing txn into the new txn. - */ -static svn_error_t * -do_switch(svnmover_wc_t *wc, - svn_revnum_t revision, - svn_branch_state_t *target_branch, - apr_pool_t *scratch_pool) -{ - const char *target_branch_id - = svn_branch_get_id(target_branch, scratch_pool); - /* Keep hold of the previous WC txn */ - svn_branch_state_t *previous_base_br = wc->base->branch; - svn_branch_state_t *previous_working_br = wc->working->branch; - svn_boolean_t has_local_changes = TRUE /* ### wc_has_local_changes(wc) */; - - if (has_local_changes - && svn_branch_root_eid(target_branch) - != svn_branch_root_eid(previous_base_br)) - { - return svn_error_createf(SVN_ERR_BRANCHING, NULL, - _("Cannot switch to branch '%s' and preserve " - "local changes as the new root element e%d " - "is not related to the old root element e%d"), - target_branch_id, - svn_branch_root_eid(target_branch), - svn_branch_root_eid(previous_base_br)); - } - - /* Complete the old edit drive into the 'WC' txn */ - SVN_ERR(svn_branch_txn_sequence_point(wc->edit_txn, scratch_pool)); - - /* Check out a new WC, re-using the same data object */ - SVN_ERR(wc_checkout(wc, revision, target_branch_id, scratch_pool)); - - if (has_local_changes) - { - svn_branch_el_rev_id_t *yca, *src, *tgt; - conflict_storage_t *conflicts; - - /* Merge changes from the old into the new WC */ - yca = svn_branch_el_rev_id_create(previous_base_br, - svn_branch_root_eid(previous_base_br), - previous_base_br->txn->rev, - scratch_pool); - src = svn_branch_el_rev_id_create(previous_working_br, - svn_branch_root_eid(previous_working_br), - SVN_INVALID_REVNUM, scratch_pool); - tgt = svn_branch_el_rev_id_create(wc->working->branch, - svn_branch_root_eid(wc->working->branch), - SVN_INVALID_REVNUM, scratch_pool); - SVN_ERR(svn_branch_merge(wc->edit_txn, &conflicts, - src, tgt, yca, scratch_pool)); - - if (apr_hash_count(conflicts->single_element_conflicts) - || apr_hash_count(conflicts->name_clash_conflicts) - || apr_hash_count(conflicts->orphan_conflicts)) - { - SVN_ERR(display_conflicts(conflicts, "switch: ", scratch_pool)); - return svn_error_createf( - SVN_ERR_BRANCHING, NULL, - _("Switch failed because of conflicts: " - "%d single-element conflicts, " - "%d name-clash conflicts, " - "%d orphan conflicts"), - apr_hash_count(conflicts->single_element_conflicts), - apr_hash_count(conflicts->name_clash_conflicts), - apr_hash_count(conflicts->orphan_conflicts)); - } - else - { - SVN_DBG(("Switch completed: no conflicts")); - } - - /* ### TODO: If the merge raises conflicts, either revert to the - pre-update state or store and handle the conflicts. Currently - this just leaves the merge partially done and raises an error. */ - } - - return SVN_NO_ERROR; -} - -/* */ -typedef struct diff_item_t -{ - int eid; - svn_element_content_t *e0, *e1; - const char *relpath0, *relpath1; - svn_boolean_t modified, reparented, renamed; -} diff_item_t; - -/* Return differences between branch subtrees S_LEFT and S_RIGHT. - * - * Set *DIFF_CHANGES to a hash of (eid -> diff_item_t). - * - * ### This requires 'subtrees' only in order to produce the 'relpath' - * fields in the output. Other than that, it would work with arbitrary - * sets of elements. - */ -static svn_error_t * -subtree_diff(apr_hash_t **diff_changes, - svn_branch_subtree_t *s_left, - svn_branch_subtree_t *s_right, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - apr_hash_t *diff_left_right; - apr_hash_index_t *hi; - - *diff_changes = apr_hash_make(result_pool); - - SVN_ERR(element_differences(&diff_left_right, - s_left->tree, s_right->tree, - result_pool, scratch_pool)); - - for (hi = apr_hash_first(scratch_pool, diff_left_right); - hi; hi = apr_hash_next(hi)) - { - int eid = svn_int_hash_this_key(hi); - svn_element_content_t **e_pair = apr_hash_this_val(hi); - svn_element_content_t *e0 = e_pair[0], *e1 = e_pair[1]; - - if (e0 || e1) - { - diff_item_t *item = apr_palloc(result_pool, sizeof(*item)); - - item->eid = eid; - item->e0 = e0; - item->e1 = e1; - item->relpath0 = e0 ? svn_element_tree_get_path_by_eid( - s_left->tree, eid, result_pool) : NULL; - item->relpath1 = e1 ? svn_element_tree_get_path_by_eid( - s_right->tree, eid, result_pool) : NULL; - item->reparented = (e0 && e1 && e0->parent_eid != e1->parent_eid); - item->renamed = (e0 && e1 && strcmp(e0->name, e1->name) != 0); - - svn_int_hash_set(*diff_changes, eid, item); - } - } - - return SVN_NO_ERROR; -} - -/* Find the relative order of diff items A and B, according to the - * "major path" of each. The major path means its right-hand relpath, if - * it exists on the right-hand side of the diff, else its left-hand relpath. - * - * Return negative/zero/positive when A sorts before/equal-to/after B. - */ -static int -diff_ordering_major_paths(const struct svn_sort__item_t *a, - const struct svn_sort__item_t *b) -{ - const diff_item_t *item_a = a->value, *item_b = b->value; - int deleted_a = (item_a->e0 && ! item_a->e1); - int deleted_b = (item_b->e0 && ! item_b->e1); - const char *major_path_a = (item_a->e1 ? item_a->relpath1 : item_a->relpath0); - const char *major_path_b = (item_b->e1 ? item_b->relpath1 : item_b->relpath0); - - /* Sort deleted items before all others */ - if (deleted_a != deleted_b) - return deleted_b - deleted_a; - - /* Sort by path */ - return svn_path_compare_paths(major_path_a, major_path_b); -} - -/* Display differences between subtrees LEFT and RIGHT, which are subtrees - * of branches LEFT_BID and RIGHT_BID respectively. - * - * Use EDITOR to fetch content when needed. - * - * Write a line containing HEADER before any other output, if it is not - * null. Write PREFIX at the start of each line of output, including any - * header line. PREFIX and HEADER should contain no end-of-line characters. - * - * The output refers to paths or to elements according to THE_UI_MODE. - */ -static svn_error_t * -show_subtree_diff(svn_branch_subtree_t *left, - const char *left_bid, - svn_branch_subtree_t *right, - const char *right_bid, - const char *prefix, - const char *header, - apr_pool_t *scratch_pool) -{ - apr_hash_t *diff_changes; - SVN_ITER_T(diff_item_t) *ai; - - SVN_ERR_ASSERT(left && left->tree->root_eid >= 0 - && right && right->tree->root_eid >= 0); - - SVN_ERR(subtree_diff(&diff_changes, left, right, - scratch_pool, scratch_pool)); - - if (header && apr_hash_count(diff_changes)) - notify("%s%s", prefix, header); - - for (SVN_HASH_ITER_SORTED(ai, diff_changes, - (the_ui_mode == UI_MODE_EIDS) - ? sort_compare_items_by_eid - : diff_ordering_major_paths, - scratch_pool)) - { - diff_item_t *item = ai->val; - svn_element_content_t *e0 = item->e0, *e1 = item->e1; - char status_mod = (e0 && e1) ? 'M' : e0 ? 'D' : 'A'; - - /* For a deleted element whose parent was also deleted, mark it is - less interesting, somehow. (Or we could omit it entirely.) */ - if (status_mod == 'D') - { - diff_item_t *parent_item - = svn_int_hash_get(diff_changes, e0->parent_eid); - - if (parent_item && ! parent_item->e1) - status_mod = 'd'; - } - - if (the_ui_mode == UI_MODE_PATHS) - { - const char *major_path = (e1 ? item->relpath1 : item->relpath0); - const char *from = ""; - - if (item->reparented || item->renamed) - { - if (! item->reparented) - from = apr_psprintf(scratch_pool, - " (renamed from .../%s)", - e0->name); - else if (! item->renamed) - from = apr_psprintf(scratch_pool, - " (moved from %s/...)", - svn_relpath_dirname(item->relpath0, - scratch_pool)); - else - from = apr_psprintf(scratch_pool, - " (moved+renamed from %s)", - item->relpath0); - } - notify("%s%c%c%c %s%s%s", - prefix, - status_mod, - item->reparented ? 'v' : ' ', item->renamed ? 'r' : ' ', - major_path, - subtree_subbranch_str(e0 ? left : right, - e0 ? left_bid : right_bid, - item->eid, scratch_pool), - from); - } - else - { - notify("%s%c%c%c e%-3d %s%s%s%s%s", - prefix, - status_mod, - item->reparented ? 'v' : ' ', item->renamed ? 'r' : ' ', - item->eid, - e1 ? peid_name(e1, scratch_pool) : "", - subtree_subbranch_str(e0 ? left : right, - e0 ? left_bid : right_bid, - item->eid, scratch_pool), - e0 && e1 ? " (from " : "", - e0 ? peid_name(e0, scratch_pool) : "", - e0 && e1 ? ")" : ""); - } - } - - return SVN_NO_ERROR; -} - -typedef svn_error_t * -svn_branch_diff_func_t(svn_branch_subtree_t *left, - const char *left_bid, - svn_branch_subtree_t *right, - const char *right_bid, - const char *prefix, - const char *header, - apr_pool_t *scratch_pool); - -/* Display differences between subtrees LEFT and RIGHT. - * - * Recurse into sub-branches. - */ -static svn_error_t * -subtree_diff_r(svn_branch_subtree_t *left, - svn_revnum_t left_rev, - const char *left_bid, - const char *left_rrpath, - svn_branch_subtree_t *right, - svn_revnum_t right_rev, - const char *right_bid, - const char *right_rrpath, - svn_branch_diff_func_t diff_func, - const char *prefix, - apr_pool_t *scratch_pool) -{ - const char *left_str - = left ? apr_psprintf(scratch_pool, "r%ld:%s:e%d at /%s", - left_rev, left_bid, left->tree->root_eid, left_rrpath) - : NULL; - const char *right_str - = right ? apr_psprintf(scratch_pool, "r%ld:%s:e%d at /%s", - right_rev, right_bid, right->tree->root_eid, right_rrpath) - : NULL; - const char *header; - apr_hash_t *subbranches_l, *subbranches_r, *subbranches_all; - apr_hash_index_t *hi; - - SVN_DBG(("subtree_diff_r: l='%s' r='%s'", - left ? left_rrpath : "<nil>", - right ? right_rrpath : "<nil>")); - - if (!left) - { - header = apr_psprintf(scratch_pool, - "--- added branch %s", - right_str); - notify("%s%s", prefix, header); - } - else if (!right) - { - header = apr_psprintf(scratch_pool, - "--- deleted branch %s", - left_str); - notify("%s%s", prefix, header); - } - else - { - if (strcmp(left_str, right_str) == 0) - { - header = apr_psprintf( - scratch_pool, "--- diff branch %s", - left_str); - } - else - { - header = apr_psprintf( - scratch_pool, "--- diff branch %s : %s", - left_str, right_str); - } - SVN_ERR(diff_func(left, left_bid, right, right_bid, - prefix, header, - scratch_pool)); - } - - /* recurse into each subbranch that exists in LEFT and/or in RIGHT */ - subbranches_l = left ? left->subbranches : apr_hash_make(scratch_pool); - subbranches_r = right ? right->subbranches : apr_hash_make(scratch_pool); - subbranches_all = apr_hash_overlay(scratch_pool, - subbranches_l, subbranches_r); - - for (hi = apr_hash_first(scratch_pool, subbranches_all); - hi; hi = apr_hash_next(hi)) - { - int e = svn_int_hash_this_key(hi); - svn_branch_subtree_t *sub_left = NULL, *sub_right = NULL; - const char *sub_left_bid = NULL, *sub_right_bid = NULL; - const char *sub_left_rrpath = NULL, *sub_right_rrpath = NULL; - - /* recurse */ - if (left) - { - sub_left = svn_branch_subtree_get_subbranch_at_eid(left, e, - scratch_pool); - if (sub_left) - { - const char *relpath - = svn_element_tree_get_path_by_eid(left->tree, e, scratch_pool); - - sub_left_bid = svn_branch_id_nest(left_bid, e, scratch_pool); - sub_left_rrpath = svn_relpath_join(left_rrpath, relpath, - scratch_pool); - } - } - if (right) - { - sub_right = svn_branch_subtree_get_subbranch_at_eid(right, e, - scratch_pool); - if (sub_right) - {
[... 2596 lines stripped ...]
