diff options
Diffstat (limited to 'tools/dev/svnmover/merge3.c')
-rw-r--r-- | tools/dev/svnmover/merge3.c | 1399 |
1 files changed, 1399 insertions, 0 deletions
diff --git a/tools/dev/svnmover/merge3.c b/tools/dev/svnmover/merge3.c new file mode 100644 index 0000000..ba32fc5 --- /dev/null +++ b/tools/dev/svnmover/merge3.c @@ -0,0 +1,1399 @@ +/* + * merge3.c: 3-way merging + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include <apr_lib.h> + +#include "svn_hash.h" +#include "svn_iter.h" +#include "svn_client.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "svn_props.h" +#include "svn_string.h" +#include "svn_dirent_uri.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_sorts_private.h" +#include "private/svn_client_private.h" + +#include "svnmover.h" + +#include "svn_private_config.h" + + +/* ====================================================================== */ + +#define is_branch_root_element(branch, eid) \ + (svn_branch__root_eid(branch) == (eid)) + +/* 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. + */ +static const char * +subbranch_str(svn_branch__state_t *branch, + int eid, + apr_pool_t *result_pool) +{ + svn_branch__state_t *subbranch; + + svn_error_clear(svn_branch__get_subbranch_at_eid(branch, &subbranch, eid, + result_pool)); + return branch_str(subbranch, result_pool); +} + +/* Return the longest known relative path leading to element EID in ELEMENTS. + * + * Set *BASE_EID_P to -1 if this path is rooted at the branch root; + * otherwise, set *BASE_EID_P to the EID from which the path is relative, + * In the latter case, element *BASE_EID_P is not found in ELEMENTS. + * + * If CYCLE_CONFLICTS is non-null, it maps each EID involved in a cycle to + * [something]. If null, assume there are no cycles. + * + * If there is a cycle, set *BASE_EID_P to the EID of the nearest element + * that is part of a cycle and return the path relative to that element. + */ +static const char * +partial_relpath(int *base_eid_p, + svn_element__tree_t *elements, + apr_hash_t *cycle_conflicts, + int eid, + apr_pool_t *result_pool) +{ + const char *s = ""; + int this_eid = eid; + svn_element__content_t *e; + + while ((e = svn_element__tree_get(elements, this_eid)) + && (e->parent_eid != -1)) + { + s = svn_relpath_join(e->name, s, result_pool); + + this_eid = e->parent_eid; + + /* Detect cycles */ + if (cycle_conflicts && svn_eid__hash_get(cycle_conflicts, this_eid)) + { + /* Cycle detected */ + e = NULL; + break; + } + } + + if (base_eid_p) + { + if (e) + { + /* We reached the root element */ + *base_eid_p = -1; + } + else + { + /* We came to this nonexistent or cyclic parent element */ + *base_eid_p = this_eid; + } + } + return s; +} + +/* */ +static svn_error_t * +display_relpath(const char **s_p, + svn_branch__state_t *branch, + apr_hash_t *cycle_conflicts, + int eid, + apr_pool_t *result_pool) +{ + svn_element__tree_t *elements; + int base_eid; + const char *s; + + SVN_ERR(svn_branch__state_get_elements(branch, &elements, result_pool)); + s = partial_relpath(&base_eid, elements, cycle_conflicts, eid, result_pool); + + if (base_eid == -1) + s = apr_psprintf(result_pool, "/%s", s); + else if (base_eid == eid) + s = "<nil>"; + else if (the_ui_mode == UI_MODE_PATHS) + s = svn_relpath_join("...", s, result_pool); + else + { + const char *eid_str = apr_psprintf(result_pool, "<e%d>", base_eid); + s = svn_relpath_join(eid_str, s, result_pool); + } + *s_p = s; + return SVN_NO_ERROR; +} + +/* Set *S_P to a string describing the identity of element EID. */ +static svn_error_t * +merged_element_id_str(const char **s_p, + conflict_storage_t *conflict_storage, + int eid, + apr_pool_t *result_pool) +{ + const char *s_yca, *s_src, *s_tgt, *s_merged; + + SVN_ERR(display_relpath(&s_yca, conflict_storage->yca_branch, NULL, + eid, result_pool)); + SVN_ERR(display_relpath(&s_src, conflict_storage->src_branch, NULL, + eid, result_pool)); + SVN_ERR(display_relpath(&s_tgt, conflict_storage->tgt_branch, conflict_storage->cycle_conflicts, + eid, result_pool)); + SVN_ERR(display_relpath(&s_merged, conflict_storage->merged_branch, + conflict_storage->cycle_conflicts, + eid, result_pool)); + *s_p = apr_psprintf(result_pool, + "yca=%s, side1=%s, side2=%s, merged=%s", + s_yca, s_src, s_tgt, s_merged); + if (the_ui_mode == UI_MODE_EIDS) + { + *s_p = apr_psprintf(result_pool, + "e%d (%s)", eid, *s_p); + } + 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 ? svn_element__content_dup(yca, result_pool) : NULL; + c->side1 = side1 ? svn_element__content_dup(side1, result_pool) : NULL; + c->side2 = side2 ? svn_element__content_dup(side2, result_pool) : NULL; + return c; +} + +static element_merge3_conflict_t * +element_merge3_conflict_dup(element_merge3_conflict_t *old_conflict, + apr_pool_t *result_pool) +{ + return element_merge3_conflict_create(old_conflict->yca, + old_conflict->side1, + old_conflict->side2, result_pool); +} + +static svn_error_t * +element_merge3_conflict_str(const char **s_p, + conflict_storage_t *conflict_storage, + element_merge3_conflict_t *c, + int eid, + apr_pool_t *result_pool) +{ + const char *id_str; + + SVN_ERR(merged_element_id_str(&id_str, conflict_storage, + eid, result_pool)); + *s_p = apr_psprintf(result_pool, + "element-merge conflict:\n" + " %s", + id_str); + return SVN_NO_ERROR; +} + +/* 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; +} + +static svn_error_t * +name_clash_conflict_str(const char **s_p, + conflict_storage_t *conflict_storage, + name_clash_conflict_t *c, + apr_pool_t *result_pool) +{ + apr_hash_index_t *hi2; + const char *s = "name-clash conflict: elements"; + + for (hi2 = apr_hash_first(result_pool, c->elements); + hi2; hi2 = apr_hash_next(hi2)) + { + int eid = svn_eid__hash_this_key(hi2); + const char *id_str; + + SVN_ERR(merged_element_id_str(&id_str, conflict_storage, + eid, result_pool)); + s = apr_psprintf(result_pool, + "%s\n" + " %s", + s, id_str); + } + *s_p = s; + return SVN_NO_ERROR; +} + +/* A cycle conflict description. + */ +typedef struct cycle_conflict_t +{ + /* All EIDs that conflict with each other: hash of (eid -> irrelevant). */ + apr_hash_t *elements; +} cycle_conflict_t; + +static cycle_conflict_t * +cycle_conflict_create(apr_pool_t *result_pool) +{ + cycle_conflict_t *c = apr_pcalloc(result_pool, sizeof(*c)); + + c->elements = apr_hash_make(result_pool); + return c; +} + +static svn_error_t * +cycle_conflict_str(const char **s_p, + conflict_storage_t *conflict_storage, + cycle_conflict_t *c, + int eid, + apr_pool_t *result_pool) +{ + svn_element__content_t *element = svn_eid__hash_get(c->elements, eid); + const char *s + = apr_psprintf(result_pool, "element '%s' has cyclic parentage", + element->name); + int this_eid = eid; + + do + { + const char *id_str; + + SVN_ERR(merged_element_id_str(&id_str, conflict_storage, + this_eid, result_pool)); + s = apr_psprintf(result_pool, + "%s\n" + " %s", + s, id_str); + element = svn_eid__hash_get(c->elements, this_eid); + this_eid = element->parent_eid; + } + while (this_eid != eid); + + *s_p = s; + return SVN_NO_ERROR; +} + +/* 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 = svn_element__content_dup(element, result_pool); + return c; +} + +static svn_error_t * +orphan_conflict_str(const char **s_p, + conflict_storage_t *conflict_storage, + orphan_conflict_t *c, + int eid, + apr_pool_t *result_pool) +{ + const char *id_str; + const char *parent_id_str; + + SVN_ERR(merged_element_id_str(&id_str, conflict_storage, + eid, result_pool)); + SVN_ERR(merged_element_id_str(&parent_id_str, conflict_storage, + c->element->parent_eid, result_pool)); + *s_p = apr_psprintf(result_pool, + "orphan conflict: parent (%s) does not exist " + "for the following child:\n" + " %s", + parent_id_str, id_str); + return SVN_NO_ERROR; +} + +/* */ +static conflict_storage_t * +conflict_storage_create(apr_pool_t *result_pool) +{ + conflict_storage_t *c = apr_pcalloc(result_pool, sizeof(*c)); + + return c; +} + +svn_error_t * +svnmover_display_conflicts(conflict_storage_t *conflict_storage, + apr_pool_t *scratch_pool) +{ + apr_hash_index_t *hi; + + svnmover_notify(_("Conflicts:")); + + for (hi = apr_hash_first(scratch_pool, + conflict_storage->element_merge_conflicts); + hi; hi = apr_hash_next(hi)) + { + int eid = svn_eid__hash_this_key(hi); + element_merge3_conflict_t *c = apr_hash_this_val(hi); + const char *id_string = apr_psprintf(scratch_pool, "e%d", eid); + const char *c_str; + + SVN_ERR(element_merge3_conflict_str(&c_str, conflict_storage, + c, eid, scratch_pool)); + if (the_ui_mode == UI_MODE_EIDS) + { + svnmover_notify(" %s: %s", id_string, c_str); + } + else + { + svnmover_notify(" %s", c_str); + } + } + for (hi = apr_hash_first(scratch_pool, + conflict_storage->name_clash_conflicts); + hi; hi = apr_hash_next(hi)) + { + const char *id_string = apr_hash_this_key(hi); + name_clash_conflict_t *c = apr_hash_this_val(hi); + const char *c_str; + + SVN_ERR(name_clash_conflict_str(&c_str, conflict_storage, + c, scratch_pool)); + if (the_ui_mode == UI_MODE_EIDS) + { + svnmover_notify(" %s: %s", id_string, c_str); + } + else + { + svnmover_notify(" %s", c_str); + } + } + for (hi = apr_hash_first(scratch_pool, + conflict_storage->cycle_conflicts); + hi; hi = apr_hash_next(hi)) + { + int eid = svn_eid__hash_this_key(hi); + cycle_conflict_t *c = apr_hash_this_val(hi); + const char *id_string = apr_psprintf(scratch_pool, "e%d", eid); + const char *c_str; + + SVN_ERR(cycle_conflict_str(&c_str, conflict_storage, + c, eid, scratch_pool)); + if (the_ui_mode == UI_MODE_EIDS) + { + svnmover_notify(" %s: %s", id_string, c_str); + } + else + { + svnmover_notify(" %s", c_str); + } + } + for (hi = apr_hash_first(scratch_pool, + conflict_storage->orphan_conflicts); + hi; hi = apr_hash_next(hi)) + { + int eid = svn_eid__hash_this_key(hi); + orphan_conflict_t *c = apr_hash_this_val(hi); + const char *id_string = apr_psprintf(scratch_pool, "e%d", eid); + const char *c_str; + + SVN_ERR(orphan_conflict_str(&c_str, conflict_storage, + c, eid, scratch_pool)); + if (the_ui_mode == UI_MODE_EIDS) + { + svnmover_notify(" %s: %s", id_string, c_str); + } + else + { + svnmover_notify(" %s", c_str); + } + } + + svnmover_notify(_("Summary of conflicts:\n" + " %d element-merge conflicts\n" + " %d name-clash conflicts\n" + " %d cycle conflicts\n" + " %d orphan conflicts\n"), + apr_hash_count(conflict_storage->element_merge_conflicts), + apr_hash_count(conflict_storage->name_clash_conflicts), + apr_hash_count(conflict_storage->cycle_conflicts), + apr_hash_count(conflict_storage->orphan_conflicts)); + return SVN_NO_ERROR; +} + +enum conflict_kind_t { conflict_kind_element_merge, + conflict_kind_clash, + conflict_kind_cycle, + conflict_kind_orphan }; + +/* */ +typedef struct conflict_object_t +{ + enum conflict_kind_t conflict_kind; + apr_hash_t *conflicts; + const void *key; +} conflict_object_t; + +/* */ +static conflict_object_t * +conflict_object_create(enum conflict_kind_t conflict_kind, + apr_hash_t *conflicts, + const void *key, + apr_pool_t *result_pool) +{ + conflict_object_t *c = apr_pcalloc(result_pool, sizeof(*c)); + + c->conflict_kind = conflict_kind; + c->conflicts = conflicts; + c->key = (conflict_kind == conflict_kind_clash) + ? apr_pstrdup(result_pool, key) + : apr_pmemdup(result_pool, key, sizeof(int)); + return c; +} + +static svn_error_t * +find_conflict(conflict_object_t **conflict_p, + conflict_storage_t *conflicts, + const char *id_string, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + *conflict_p = NULL; + + if (id_string[0] == 'e') + { + int which_eid = atoi(id_string + 1); + + if (svn_eid__hash_get(conflicts->element_merge_conflicts, which_eid)) + { + *conflict_p + = conflict_object_create(conflict_kind_element_merge, + conflicts->element_merge_conflicts, + &which_eid, result_pool); + } + if (svn_eid__hash_get(conflicts->cycle_conflicts, which_eid)) + { + *conflict_p + = conflict_object_create(conflict_kind_cycle, + conflicts->cycle_conflicts, + &which_eid, result_pool); + } + if (svn_eid__hash_get(conflicts->orphan_conflicts, which_eid)) + { + *conflict_p + = conflict_object_create(conflict_kind_orphan, + conflicts->orphan_conflicts, + &which_eid, result_pool); + } + } + else + { + if (svn_hash_gets(conflicts->name_clash_conflicts, id_string)) + { + *conflict_p + = conflict_object_create(conflict_kind_clash, + conflicts->name_clash_conflicts, + id_string, result_pool); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svnmover_conflict_resolved(conflict_storage_t *conflicts, + const char *id_string, + apr_pool_t *scratch_pool) +{ + conflict_object_t *conflict; + + SVN_ERR(find_conflict(&conflict, conflicts, id_string, + scratch_pool, scratch_pool)); + if (! conflict) + { + return svn_error_createf(SVN_BRANCH__ERR, NULL, + _("Conflict '%s' not found"), id_string); + } + + if (conflict->conflict_kind == conflict_kind_clash) + { + svn_hash_sets(conflict->conflicts, conflict->key, NULL); + } + else + { + apr_hash_set(conflict->conflicts, conflict->key, sizeof (int), NULL); + } + svnmover_notify("Marked conflict '%s' as resolved", id_string); + return SVN_NO_ERROR; +} + +svn_boolean_t +svnmover_any_conflicts(const conflict_storage_t *conflicts) +{ + return conflicts + && (apr_hash_count(conflicts->element_merge_conflicts) + || apr_hash_count(conflicts->name_clash_conflicts) + || apr_hash_count(conflicts->cycle_conflicts) + || apr_hash_count(conflicts->orphan_conflicts)); +} + +/* 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)); + + /* merge the parent-eid */ + if (side1->parent_eid == yca->parent_eid) + { + result->parent_eid = side2->parent_eid; + } + else if (side2->parent_eid == yca->parent_eid) + { + result->parent_eid = side1->parent_eid; + } + else if (policy->merge_double_reparent + && side1->parent_eid == side2->parent_eid) + { + /*SVN_DBG(("e%d double reparent: e%d -> { e%d | e%d }", + eid, yca->parent_eid, side1->parent_eid, side2->parent_eid));*/ + result->parent_eid = side1->parent_eid; + } + else + { + /*SVN_DBG(("e%d conflict: parent: e%d -> { e%d | e%d }", + eid, yca->parent_eid, side1->parent_eid, side2->parent_eid));*/ + conflict = TRUE; + } + + /* merge the name */ + if (strcmp(side1->name, yca->name) == 0) + { + result->name = side2->name; + } + 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, + svn_branch__state_t *edit_branch, + 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, + svn_branch__state_t *edit_branch, + 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__state_t *tgt_subbranch; + svn_branch__state_t *yca_subbranch; + 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; + + SVN_ERR(svn_branch__get_subbranch_at_eid(src->branch, &src_subbranch, + eid, scratch_pool)); + SVN_ERR(svn_branch__get_subbranch_at_eid(tgt->branch, &tgt_subbranch, + eid, scratch_pool)); + SVN_ERR(svn_branch__get_subbranch_at_eid(yca->branch, &yca_subbranch, + eid, scratch_pool)); + 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; + const char *new_branch_id + = svn_branch__id_nest(svn_branch__get_id(edit_branch, scratch_pool), + eid, scratch_pool); + svn_branch__rev_bid_eid_t *from + = svn_branch__rev_bid_eid_create(tgt_subbranch->txn->rev, + svn_branch__get_id(tgt_subbranch, + scratch_pool), + svn_branch__root_eid(tgt_subbranch), + scratch_pool); + svn_branch__state_t *edit_subbranch; + + SVN_ERR(svn_branch__txn_open_branch(edit_txn, &edit_subbranch, + new_branch_id, from->eid, from, + scratch_pool, scratch_pool)); + + /* subbranch possibly changed in source => merge */ + SVN_ERR(branch_merge_subtree_r(edit_txn, edit_subbranch, + &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(edit_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_open_branch(edit_txn, NULL /*new_branch_p*/, + new_branch_id, from->eid, from, + scratch_pool, scratch_pool)); + } + else if (subbr_tgt) /* added on target branch */ + { + const char *new_branch_id + = svn_branch__id_nest(svn_branch__get_id(edit_branch, scratch_pool), + eid, scratch_pool); + svn_branch__rev_bid_eid_t *from + = svn_branch__rev_bid_eid_create(tgt_subbranch->txn->rev, + svn_branch__get_id(tgt_subbranch, + scratch_pool), + svn_branch__root_eid(tgt_subbranch), + scratch_pool); + + SVN_ERR(svn_branch__txn_open_branch(edit_txn, NULL /*new_branch_p*/, + new_branch_id, from->eid, from, + scratch_pool, scratch_pool)); + } + 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_element__tree_t *elements; + svn_eid__hash_iter_t *ei; + int prev_eid = -1; + svn_element__content_t *prev_element = NULL; + + SVN_ERR(svn_branch__state_get_elements(branch, &elements, scratch_pool)); + for (SVN_EID__HASH_ITER_SORTED(ei, elements->e_map, + sort_compare_items_by_peid_and_name, + scratch_pool)) + { + int eid = ei->eid; + svn_element__content_t *element = ei->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_eid__hash_set(c->elements, eid, &c); + svn_eid__hash_set(c->elements, prev_eid, &c); + } + prev_eid = eid; + prev_element = element; + } + + *clashes_p = clashes; + return SVN_NO_ERROR; +} + +/* For each element in the cycle starting at ONE_EID in EIDS_VISITED, + * record an entry in CYCLES[this_eid] mapping to a cycle_conflict_t. + * Each such new entry will point to the same cycle_conflict_t. This + * cycle_conflict_t will contain the list of elements in the cycle. + * + * ONE_EID should identify a member of a simple cycle, not an element + * that merely has a parent or ancestor in a simple cycle. + */ +static svn_error_t * +record_cycle(apr_hash_t *cycles, + apr_hash_t *eids_visited, + int one_eid, + apr_pool_t *result_pool) +{ + cycle_conflict_t *c = cycle_conflict_create(result_pool); + int this_eid = one_eid; + + do + { + svn_element__content_t *element + = svn_eid__hash_get(eids_visited, this_eid); + + svn_eid__hash_set(cycles, this_eid, c); + svn_eid__hash_set(c->elements, this_eid, element); + + this_eid = element->parent_eid; + assert(this_eid != -1); + } + while (this_eid != one_eid); + + return SVN_NO_ERROR; +} + +/* Return all (eid -> cycle_conflict_t) cycle conflicts in BRANCH. + + * ### This implementation is crude: it finds all cycles, but doesn't + * report them minimally. It reports each element that leads to a cycle, + * without isolating the minimal cycles nor eliminating duplicates. + */ +static svn_error_t * +detect_cycles(apr_hash_t **cycles_p, + svn_branch__state_t *branch, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *cycles = apr_hash_make(result_pool); + apr_hash_index_t *hi; + svn_element__tree_t *elements; + + SVN_ERR(svn_branch__state_get_elements(branch, &elements, scratch_pool)); + for (hi = apr_hash_first(scratch_pool, elements->e_map); + hi; hi = apr_hash_next(hi)) + { + int eid = svn_eid__hash_this_key(hi); + svn_element__content_t *element = apr_hash_this_val(hi); + apr_hash_t *eids_visited; + int this_eid; + + /* If the element EID is already recorded as a member of a cycle, + there's nothing more to do for it. */ + if (svn_eid__hash_get(cycles, eid)) + { + continue; + } + + eids_visited = apr_hash_make(scratch_pool); + + /* See if we can trace the parentage of EID back to the branch root + without finding a cycle. If we find a cycle, store a conflict. */ + for (this_eid = eid; + element = svn_eid__hash_get(elements->e_map, this_eid), + element && element->parent_eid != -1; + this_eid = element->parent_eid) + { + svn_eid__hash_set(eids_visited, this_eid, element); + + /* If the element EID is attached to an element of a previously + detected cycle, then it's not interesting in itself. */ + if (svn_eid__hash_get(cycles, element->parent_eid)) + { + break; + } + /* If this element's parent-EID is already in the path of EIDs + visited from EID to the root, then we have found a cycle. */ + if (svn_eid__hash_get(eids_visited, element->parent_eid)) + { + SVN_ERR(record_cycle(cycles, eids_visited, this_eid, + result_pool)); + break; + } + } + } + + *cycles_p = cycles; + 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); + apr_hash_index_t *hi; + svn_element__tree_t *elements; + + SVN_ERR(svn_branch__state_get_elements(branch, &elements, scratch_pool)); + for (hi = apr_hash_first(scratch_pool, elements->e_map); + hi; hi = apr_hash_next(hi)) + { + int eid = svn_eid__hash_this_key(hi); + svn_element__content_t *element = apr_hash_this_val(hi); + + 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_eid__hash_set(orphans, eid, c); + } + } + + *orphans_p = orphans; + return SVN_NO_ERROR; +} + +/* Merge ... + * + * The elements to merge are the union of the elements in the three input + * subtrees (SRC, TGT, YCA). + * + * Merge any sub-branches in the same way, recursively. + * + * ### TODO: Store the merge result separately, without overwriting the + * target input state, so that the three input states are all available + * for reference while resolving conflicts. + */ +static svn_error_t * +branch_merge_subtree_r(svn_branch__txn_t *edit_txn, + svn_branch__state_t *edit_branch, + 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(result_pool); + conflict_storage_t *conflict_storage = conflict_storage_create(result_pool); + svn_element__tree_t *src_elements, *tgt_elements, *yca_elements; + apr_hash_t *all_elements; + svn_eid__hash_iter_t *ei; + const merge_conflict_policy_t policy = { TRUE, TRUE, TRUE, TRUE, TRUE }; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + /*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));*/ + + svnmover_notify_v("merging into branch %s", + edit_branch->bid); + /* + 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 + */ + SVN_ERR(svn_branch__get_subtree(src->branch, &s_src, src->eid, scratch_pool)); + SVN_ERR(svn_branch__get_subtree(tgt->branch, &s_tgt, tgt->eid, scratch_pool)); + SVN_ERR(svn_branch__get_subtree(yca->branch, &s_yca, yca->eid, scratch_pool)); + + /* ALL_ELEMENTS enumerates the elements in union of subtrees YCA,SRC,TGT. */ + all_elements = hash_overlay(s_src->tree->e_map, + s_tgt->tree->e_map); + all_elements = hash_overlay(s_yca->tree->e_map, + all_elements); + + SVN_ERR(svn_branch__state_get_elements(src->branch, &src_elements, + scratch_pool)); + SVN_ERR(svn_branch__state_get_elements(tgt->branch, &tgt_elements, + scratch_pool)); + SVN_ERR(svn_branch__state_get_elements(yca->branch, &yca_elements, + scratch_pool)); + + /* Find the two changes for each element that is in any of the subtrees, + even for an element that is (for example) not in YCA or SRC but has + been moved into TGT. */ + SVN_ERR(svnmover_element_differences(&diff_yca_src, + yca_elements, src_elements, + all_elements, + scratch_pool, scratch_pool)); + /* ### We only need to know about YCA:TGT differences for elements that + differ in YCA:SRC, but right now we ask for all differences. */ + SVN_ERR(svnmover_element_differences(&diff_yca_tgt, + yca_elements, tgt_elements, + all_elements, + scratch_pool, scratch_pool)); + + for (SVN_EID__HASH_ITER_SORTED_BY_EID(ei, all_elements, scratch_pool)) + { + int eid = ei->eid; + svn_element__content_t **e_yca_src + = svn_eid__hash_get(diff_yca_src, eid); + svn_element__content_t **e_yca_tgt + = svn_eid__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, edit_branch, + 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]; + + /* If some but not all of the three subtree-root elements are branch + roots, then we will see the parentage of this element changing to + or from 'no parent' in one or both sides of the merge. We want to + ignore this part of the difference, as parentage of a subtree root + element is by definition not part of a 'subtree', so blank it out. + (If we merged it, it could break the single-rooted-tree invariant + of the target branch.) + */ + if (is_branch_root_element(src->branch, eid) + || is_branch_root_element(tgt->branch, eid) + || is_branch_root_element(yca->branch, eid)) + { + e_src = svn_element__content_create( + e_tgt->parent_eid, e_tgt->name, e_src->payload, iterpool); + e_yca = svn_element__content_create( + e_tgt->parent_eid, e_tgt->name, e_yca->payload, iterpool); + } + + element_merge(&result, &conflict, + eid, e_src, e_tgt, e_yca, + &policy, + scratch_pool, scratch_pool); + + if (conflict) + { + svnmover_notify_v("! e%d <conflict>", eid); + svn_eid__hash_set(e_conflicts, eid, + element_merge3_conflict_dup(conflict, result_pool)); + } + else + { + if (e_tgt && result) + { + svnmover_notify_v("M/V e%d %s%s", + eid, result->name, + subbranch_str(tgt->branch, eid, iterpool)); + } + else if (e_tgt) + { + svnmover_notify_v("D e%d %s%s", + eid, e_yca->name, + subbranch_str(yca->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) + { + svnmover_notify_v("A e%d %s%s", + eid, result->name, + subbranch_str(src->branch, eid, iterpool)); + } + + SVN_ERR(svn_branch__state_set_element(tgt->branch, eid, + result, iterpool)); + + if (result) + { + SVN_ERR(merge_subbranch(edit_txn, edit_branch, + src, tgt, yca, eid, iterpool)); + } + } + } + svn_pool_destroy(iterpool); + + conflict_storage->yca_branch = yca->branch; + conflict_storage->src_branch = src->branch; + conflict_storage->tgt_branch = tgt->branch; + conflict_storage->merged_branch = tgt->branch; /* ### should be != tgt */ + conflict_storage->element_merge_conflicts = e_conflicts; + SVN_ERR(detect_clashes(&conflict_storage->name_clash_conflicts, + tgt->branch, + result_pool, scratch_pool)); + SVN_ERR(detect_cycles(&conflict_storage->cycle_conflicts, + tgt->branch, + result_pool, scratch_pool)); + SVN_ERR(detect_orphans(&conflict_storage->orphan_conflicts, + tgt->branch, + result_pool, scratch_pool)); + + svnmover_notify_v("merging into branch %s -- finished", + svn_branch__get_id(tgt->branch, scratch_pool)); + + *conflict_storage_p = conflict_storage; + return SVN_NO_ERROR; +} + +svn_error_t * +svnmover_branch_merge(svn_branch__txn_t *edit_txn, + svn_branch__state_t *edit_branch, + 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 *result_pool, + apr_pool_t *scratch_pool) +{ + conflict_storage_t *conflicts; + + /*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));*/ + /*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, edit_branch, + &conflicts, + src, tgt, yca, + result_pool, scratch_pool)); + + if (conflict_storage_p) + { + if (svnmover_any_conflicts(conflicts)) + { + *conflict_storage_p = conflicts; + } + else + { + *conflict_storage_p = NULL; + } + } + return SVN_NO_ERROR; +} + |