You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by pb...@apache.org on 2010/03/25 22:16:40 UTC
svn commit: r927593 [2/3] - in /subversion/branches/1.6.x-issue3390: ./
contrib/cgi/ contrib/client-side/emacs/ contrib/hook-scripts/
contrib/server-side/ doc/user/ notes/ packages/windows-WiX/BuildSubversion/
packages/windows-innosetup/ subversion/bin...
Modified: subversion/branches/1.6.x-issue3390/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/1.6.x-issue3390/subversion/libsvn_client/merge.c?rev=927593&r1=927592&r2=927593&view=diff
==============================================================================
--- subversion/branches/1.6.x-issue3390/subversion/libsvn_client/merge.c (original)
+++ subversion/branches/1.6.x-issue3390/subversion/libsvn_client/merge.c Thu Mar 25 21:16:37 2010
@@ -131,10 +131,17 @@
* merge target and any of its subtrees which have explicit mergeinfo
* or otherwise need special attention during a merge.
*
- * CHILDREN_WITH_MERGEINFO is initially created by get_mergeinfo_paths()
- * and outside of that function and its helpers should always meet the
- * criteria dictated in get_mergeinfo_paths()'s doc string. The elements
- * of CHILDREN_WITH_MERGINFO should never be NULL.
+ * During mergeinfo unaware merges, CHILDREN_WITH_MERGEINFO is created by
+ * do_mergeinfo_unaware_dir_merge and contains only one element describing
+ * a contiguous range to be merged to the WC merge target.
+ *
+ * During mergeinfo aware merges CHILDREN_WITH_MERGEINFO is created
+ * by get_mergeinfo_paths() and outside of that function and its helpers
+ * should always meet the criteria dictated in get_mergeinfo_paths()'s doc
+ * string. The elements of CHILDREN_WITH_MERGINFO should never be NULL.
+ *
+ * For clarification on mergeinfo aware vs. mergeinfo unaware merges, see
+ * the doc string for honor_mergeinfo().
*/
/*-----------------------------------------------------------------------*/
@@ -244,6 +251,13 @@ typedef struct merge_cmd_baton_t {
reflect the merge_source_t *currently* being merged by do_merge(). */
merge_source_t merge_source;
+ /* Rangelist containing single range which describes the gap, if any,
+ in the natural history of the merge source currently being processed.
+ See http://subversion.tigris.org/issues/show_bug.cgi?id=3432.
+ Updated during each call to do_directory_merge(). May be NULL if there
+ is no gap. */
+ apr_array_header_t *implicit_src_gap;
+
svn_client_ctx_t *ctx; /* Client context for callbacks, etc. */
/* Whether invocation of the merge_file_added() callback required
@@ -591,7 +605,7 @@ tree_conflict_on_add(merge_cmd_baton_t *
set it to false.
If *HONOR_MERGEINFO is set to TRUE and the merge is not a dry run then set
- *RECORD_MERGEINFO to true, otherwise set it to false.
+ *RECORD_MERGEINFO to true, otherwise set it to false.
**/
static APR_INLINE void
mergeinfo_behavior(svn_boolean_t *honor_mergeinfo_p,
@@ -2838,7 +2852,7 @@ get_full_mergeinfo(svn_mergeinfo_t *reco
svn_boolean_t inherited;
SVN_ERR(svn_client__get_wc_or_repos_mergeinfo(recorded_mergeinfo, entry,
&inherited, FALSE,
- inherit, ra_session,
+ inherit, NULL,
target_wcpath,
adm_access, ctx, pool));
if (indirect)
@@ -3312,8 +3326,12 @@ filter_merged_revisions(svn_client__merg
been merged to CHILD->PATH and populate CHILD->REMAINING_RANGES with the
ranges that still need merging.
- URL1, REVISION1, URL2, REVISION2, TARGET_MERGEINFO, ADM_ACCESS, and CTX
- are all cascaded from the caller's arguments of the same names.
+ URL1, REVISION1, URL2, REVISION2, ADM_ACCESS, and CTX are all cascaded
+ from the caller's arguments of the same names. Note that this means URL1,
+ REVISION1, URL2, and REVISION2 adhere to the requirements noted in
+ `MERGEINFO MERGE SOURCE NORMALIZATION'.
+
+ TARGET_MERGEINFO is the working mergeinfo on CHILD.
RA_SESSION is the session for, and SOURCE_ROOT_URL is the repository root
for, the younger of URL1@REVISION1 and URL2@REVISION2.
@@ -3333,6 +3351,9 @@ filter_merged_revisions(svn_client__merg
If IS_SUBTREE is FALSE then PARENT is ignored, otherwise PARENT must
represent the nearest working copy ancestor of CHILD.
+ If not null, IMPLICIT_SRC_GAP is the gap, if any, in the natural history
+ of URL1@REVISION1:URL2@REVISION2, see merge_cmd_baton_t.implicit_src_gap.
+
NOTE: This should only be called when honoring mergeinfo.
NOTE: If PARENT is present then this function must have previously been
@@ -3354,6 +3375,7 @@ calculate_remaining_ranges(svn_client__m
const char *url2,
svn_revnum_t revision2,
svn_mergeinfo_t target_mergeinfo,
+ apr_array_header_t *implicit_src_gap,
svn_boolean_t is_subtree,
svn_boolean_t child_inherits_implicit,
svn_ra_session_t *ra_session,
@@ -3364,16 +3386,45 @@ calculate_remaining_ranges(svn_client__m
{
const char *mergeinfo_path;
const char *primary_url = (revision1 < revision2) ? url2 : url1;
+ svn_mergeinfo_t adjusted_target_mergeinfo = NULL;
/* Determine which of the requested ranges to consider merging... */
SVN_ERR(svn_client__path_relative_to_root(&mergeinfo_path, primary_url,
source_root_url, TRUE,
ra_session, NULL, pool));
+ /* Consider: CHILD might have explicit mergeinfo '/MERGEINFO_PATH:M-N'
+ where M-N fall into the gap in URL1@REVISION1:URL2@REVISION2's natural
+ history allowed by 'MERGEINFO MERGE SOURCE NORMALIZATION'. If this is
+ the case, then '/MERGEINFO_PATH:N' actually refers to a completely
+ different line of history than URL1@REVISION1:URL2@REVISION2 and we
+ *don't* want to consider those revisions merged already. */
+ if (implicit_src_gap && child->pre_merge_mergeinfo)
+ {
+ apr_array_header_t *explicit_mergeinfo_gap_ranges =
+ apr_hash_get(child->pre_merge_mergeinfo, mergeinfo_path,
+ APR_HASH_KEY_STRING);
+
+ if (explicit_mergeinfo_gap_ranges)
+ {
+ svn_mergeinfo_t gap_mergeinfo = apr_hash_make(pool);
+
+ apr_hash_set(gap_mergeinfo, mergeinfo_path, APR_HASH_KEY_STRING,
+ implicit_src_gap);
+ SVN_ERR(svn_mergeinfo__remove2(&adjusted_target_mergeinfo,
+ gap_mergeinfo, target_mergeinfo,
+ FALSE, pool, pool));
+ }
+ }
+ else
+ {
+ adjusted_target_mergeinfo = target_mergeinfo;
+ }
+
/* Initialize CHILD->REMAINING_RANGES and filter out revisions already
merged (or, in the case of reverse merges, ranges not yet merged). */
SVN_ERR(filter_merged_revisions(parent, child, entry, mergeinfo_path,
- target_mergeinfo,
+ adjusted_target_mergeinfo,
revision1, revision2,
child_inherits_implicit,
ra_session, adm_access, ctx, pool));
@@ -3496,13 +3547,148 @@ calculate_remaining_ranges(svn_client__m
return SVN_NO_ERROR;
}
+/* Helper for populate_remaining_ranges().
+
+ URL1, REVISION1, URL2, REVISION2, RA_SESSION, MERGE_SRC_CANON_PATH,
+ and MERGE_B are all cascaded from the arguments of the same name in
+ populate_remaining_ranges(). MERGE_SRC_CANON_PATH is the absolute
+ repository path of URL2.
+
+ Note: The following comments assume a forward merge, i.e.
+ REVISION1 < REVISION2. If this is a reverse merge then all the following
+ comments still apply, but with URL1 switched with URL2 and REVISION1
+ switched with REVISION2.
+
+ Like populate_remaining_ranges(), URL1@REVISION1:URL2@REVISION2 must adhere
+ to the restrictions documented in 'MERGEINFO MERGE SOURCE NORMALIZATION'.
+ These restrictions allow for a *single* gap, URL@GAP_REV1:URL2@GAP_REV2,
+ (where REVISION1 < GAP_REV1 <= GAP_REV2 < REVISION2) in
+ URL1@REVISION1:URL2@REVISION2 if URL2@REVISION2 was copied from
+ URL1@REVISION1. If such a gap exists, set *GAP_START and *GAP_END to the
+ starting and ending revisions of the gap. Otherwise set both to
+ SVN_INVALID_REVNUM.
+
+ For example, if the natural history of URL@2:URL@9 is 'trunk/:2,7-9' this
+ would indicate that trunk@7 was copied from trunk@2. This function would
+ return GAP_START:GAP_END of 2:6 in this case. Note that a path 'trunk'
+ might exist at r3-6, but it would not be on the same line of history as
+ trunk@9. */
+static svn_error_t *
+find_gaps_in_merge_source_history(svn_revnum_t *gap_start,
+ svn_revnum_t *gap_end,
+ const char *merge_src_canon_path,
+ const char *url1,
+ svn_revnum_t revision1,
+ const char *url2,
+ svn_revnum_t revision2,
+ svn_ra_session_t *ra_session,
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *scratch_pool,
+ apr_pool_t *result_pool)
+{
+ svn_mergeinfo_t implicit_src_mergeinfo;
+ svn_opt_revision_t peg_rev;
+ svn_revnum_t young_rev = MAX(revision1, revision2);
+ svn_revnum_t old_rev = MIN(revision1, revision2);
+ apr_array_header_t *rangelist;
+ const char *url = (revision2 < revision1) ? url1 : url2;
+
+ /* Start by assuming there is no gap. */
+ *gap_start = *gap_end = SVN_INVALID_REVNUM;
+
+ /* Get URL1@REVISION1:URL2@REVISION2 as mergeinfo. */
+ peg_rev.kind = svn_opt_revision_number;
+ peg_rev.value.number = young_rev;
+ SVN_ERR(svn_client__get_history_as_mergeinfo(&implicit_src_mergeinfo, url,
+ &peg_rev, young_rev, old_rev,
+ ra_session, NULL,
+ merge_b->ctx, scratch_pool));
+
+ rangelist = apr_hash_get(implicit_src_mergeinfo,
+ merge_src_canon_path,
+ APR_HASH_KEY_STRING);
+
+ if (rangelist) /* ### Can we ever not find a rangelist? */
+ {
+ /* A gap in natural history can result from either a copy or
+ a rename. If from a copy then history as mergeinfo will look
+ something like this:
+
+ '/trunk:X,Y-Z'
+
+ If from a rename it will look like this:
+
+ '/trunk_old_name:X'
+ '/trunk_new_name:Y-Z'
+
+ In both cases the gap, if it exists, is M-N, where M = X + 1 and
+ N = Y - 1.
+
+ Note that per the rules of 'MERGEINFO MERGE SOURCE NORMALIZATION' we
+ should never have multiple gaps, e.g. if we see anything like the
+ following then something is quite wrong:
+
+ '/trunk_old_name:A,B-C'
+ '/trunk_new_name:D-E'
+ */
+
+ if (rangelist->nelts > 1) /* Copy */
+ {
+ /* As mentioned above, multiple gaps *shouldn't* be possible. */
+ SVN_ERR_ASSERT(apr_hash_count(implicit_src_mergeinfo) == 1);
+
+ *gap_start = MIN(revision1, revision2);
+ *gap_end = (APR_ARRAY_IDX(rangelist,
+ rangelist->nelts - 1,
+ svn_merge_range_t *))->start;
+ }
+ else if (apr_hash_count(implicit_src_mergeinfo) > 1) /* Rename */
+ {
+ apr_array_header_t *requested_rangelist =
+ init_rangelist(MIN(revision1, revision2),
+ MAX(revision1, revision2),
+ TRUE, scratch_pool);
+ apr_array_header_t *implicit_rangelist =
+ apr_array_make(scratch_pool, 2, sizeof(svn_merge_range_t *));
+ apr_array_header_t *gap_rangelist;
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, implicit_src_mergeinfo);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ void *value;
+
+ apr_hash_this(hi, NULL, NULL, &value);
+ SVN_ERR(svn_rangelist_merge(&implicit_rangelist, value,
+ scratch_pool));
+ }
+ SVN_ERR(svn_rangelist_remove(&gap_rangelist, implicit_rangelist,
+ requested_rangelist, FALSE,
+ scratch_pool));
+
+ /* If there is anything left it is the gap. */
+ if (gap_rangelist->nelts)
+ {
+ svn_merge_range_t *gap_range =
+ APR_ARRAY_IDX(gap_rangelist, 0, svn_merge_range_t *);
+
+ *gap_start = gap_range->start;
+ *gap_end = gap_range->end;
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
/* Helper for do_directory_merge().
For each child in CHILDREN_WITH_MERGEINFO, populate that
child's remaining_ranges list. CHILDREN_WITH_MERGEINFO is expected
to be sorted in depth first order and each child must be processed in
- that order. All persistent allocations are from
- CHILDREN_WITH_MERGEINFO->pool.
+ that order. All persistent allocations are from POOL. The inheritability
+ of all calculated ranges is TRUE.
If HONOR_MERGEINFO is set, this function will actually try to be
intelligent about populating remaining_ranges list. Otherwise, it
@@ -3523,18 +3709,18 @@ populate_remaining_ranges(apr_array_head
svn_revnum_t revision1,
const char *url2,
svn_revnum_t revision2,
- svn_boolean_t inheritable,
svn_boolean_t honor_mergeinfo,
svn_ra_session_t *ra_session,
const char *parent_merge_src_canon_path,
svn_wc_adm_access_t *adm_access,
- merge_cmd_baton_t *merge_b)
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *pool)
{
- apr_pool_t *iterpool, *pool;
+ apr_pool_t *iterpool;
int merge_target_len = strlen(merge_b->target);
int i;
+ svn_revnum_t gap_start, gap_end;
- pool = children_with_mergeinfo->pool;
iterpool = svn_pool_create(pool);
/* If we aren't honoring mergeinfo or this is a --record-only merge,
@@ -3551,7 +3737,7 @@ populate_remaining_ranges(apr_array_head
range->start = revision1;
range->end = revision2;
- range->inheritable = inheritable;
+ range->inheritable = TRUE;
child->remaining_ranges =
apr_array_make(pool, 1, sizeof(svn_merge_range_t *));
@@ -3560,6 +3746,31 @@ populate_remaining_ranges(apr_array_head
return SVN_NO_ERROR;
}
+ /* If, in the merge source's history, there was a copy from a older
+ revision, then URL2 won't exist at some range M:N, where
+ REVISION1 < M < N < REVISION2. The rules of 'MERGEINFO MERGE SOURCE
+ NORMALIZATION' allow this, but we must ignore these gaps when
+ calculating what ranges remain to be merged from
+ URL1@REVISION1:URL2@REVISION2. If we don't and try to merge any part
+ of URL2@M:URL2@N we would break the editor since no part of that
+ actually exists. See http://svn.haxx.se/dev/archive-2008-11/0618.shtml.
+
+ Find the gaps in the merge target's history, if any. Eventually
+ we will adjust CHILD->REMAINING_RANGES such that we don't describe
+ non-existent paths to the editor. */
+ SVN_ERR(find_gaps_in_merge_source_history(&gap_start, &gap_end,
+ parent_merge_src_canon_path,
+ url1, revision1,
+ url2, revision2,
+ ra_session, merge_b,
+ iterpool, pool));
+
+ /* Stash any gap in the merge command baton, we'll need it later when
+ recording mergeinfo describing this merge. */
+ if (SVN_IS_VALID_REVNUM(gap_start) && SVN_IS_VALID_REVNUM(gap_end))
+ merge_b->implicit_src_gap = init_rangelist(gap_start, gap_end, TRUE,
+ pool);
+
for (i = 0; i < children_with_mergeinfo->nelts; i++)
{
const char *child_repos_path;
@@ -3606,52 +3817,6 @@ populate_remaining_ranges(apr_array_head
MIN(revision1, revision2),
adm_access, merge_b->ctx, pool));
- /* If, in the merge target's history, there was a copy from a older
- source which didn't exist at the time of the copy, then URL2 won't
- exist at some range M:N, where REVISION1 < M < N < REVISION2. If
- there were multiple instances of such copies then there will be
- multiple 'gaps' like this. The rules of 'MERGEINFO MERGE SOURCE
- NORMALIZATION' allow this, but we must ignore these gaps when
- calculating what ranges remain to be merged from
- URL1@REVISION1:URL2@REVISION2. In other words, we can't try to
- merge any part of URL2@M:URL2@N since no part of that actually
- exists and would break the editor. We can "ignore" these gaps by
- adjusting the target's implicit mergeinfo to make it look like they
- are already part of the target's natural history. */
- if (i == 0) /* CHILDREN_WITH_MERGEINFO[0] is always the target. */
- {
- apr_hash_index_t *hi;
- svn_revnum_t youngest_rev, oldest_rev;
-
- SVN_ERR(svn_mergeinfo__get_range_endpoints(
- &youngest_rev, &oldest_rev, child->implicit_mergeinfo,
- iterpool));
-
- /* Adjust the target's implicit mergeinfo so there are no gaps */
- for (hi = apr_hash_first(iterpool, child->implicit_mergeinfo);
- hi;
- hi = apr_hash_next(hi))
- {
- const void *key;
- void *value;
- const char *source_path;
- apr_array_header_t *source_rangelist, *rev1_rev2_rangelist;
-
- apr_hash_this(hi, &key, NULL, &value);
- source_path = key;
- source_rangelist = value;
- youngest_rev = (APR_ARRAY_IDX(source_rangelist,
- source_rangelist->nelts - 1,
- svn_merge_range_t *))->end;
- rev1_rev2_rangelist = init_rangelist(oldest_rev, youngest_rev,
- TRUE, iterpool);
- svn_rangelist_merge(&source_rangelist, rev1_rev2_rangelist,
- pool);
- apr_hash_set(child->implicit_mergeinfo, source_path,
- APR_HASH_KEY_STRING, source_rangelist);
- }
- }
-
/* If CHILD isn't the merge target find its parent. */
if (i > 0)
{
@@ -3677,11 +3842,82 @@ populate_remaining_ranges(apr_array_head
child_url1, revision1,
child_url2, revision2,
child->pre_merge_mergeinfo,
+ merge_b->implicit_src_gap,
i > 0, /* is subtree */
child_inherits_implicit,
ra_session, child_entry,
adm_access, merge_b->ctx,
pool));
+
+ /* Deal with any gap in URL1@REVISION1:URL2@REVISION2's natural history.
+
+ If the gap is a proper subset of CHILD->REMAINING_RANGES then we can
+ safely ignore it since we won't describe this path/rev pair.
+
+ If the gap exactly matches or is a superset of a range in
+ CHILD->REMAINING_RANGES then we must remove that range so we don't
+ attempt to describe non-existent paths via the reporter, this will
+ break the editor and our merge.
+
+ If the gap adjoins or overlaps a range in CHILD->REMAINING_RANGES
+ then we must *add* the gap so we span the missing revisions. */
+ if (child->remaining_ranges->nelts
+ && merge_b->implicit_src_gap)
+ {
+ int j;
+ svn_revnum_t start, end;
+ svn_boolean_t proper_subset = FALSE;
+ svn_boolean_t equals = FALSE;
+ svn_boolean_t overlaps_or_adjoins = FALSE;
+
+ /* If this is a reverse merge reorder CHILD->REMAINING_RANGES
+ so it will work with the svn_rangelist_* APIs below. */
+ if (revision1 > revision2)
+ svn_rangelist_reverse(child->remaining_ranges, iterpool);
+
+ for (j = 0; j < child->remaining_ranges->nelts; j++)
+ {
+ start = (APR_ARRAY_IDX(child->remaining_ranges, j,
+ svn_merge_range_t *))->start;
+ end = (APR_ARRAY_IDX(child->remaining_ranges, j,
+ svn_merge_range_t *))->end;
+ if ((start <= gap_start && gap_end < end)
+ || (start < gap_start && gap_end <= end))
+ {
+ proper_subset = TRUE;
+ break;
+ }
+ else if ((gap_start == start) && (end == gap_end))
+ {
+ equals = TRUE;
+ break;
+ }
+ else if (gap_start <= end && start <= gap_end) /* intersect */
+ {
+ overlaps_or_adjoins = TRUE;
+ break;
+ }
+ }
+
+ if (!proper_subset)
+ {
+ /* We need to make adjustements. Remove from, or add the gap
+ to, CHILD->REMAINING_RANGES as appropriate. */
+
+ if (overlaps_or_adjoins)
+ SVN_ERR(svn_rangelist_merge(&(child->remaining_ranges),
+ merge_b->implicit_src_gap,
+ pool));
+ else /* equals == TRUE */
+ SVN_ERR(svn_rangelist_remove(&(child->remaining_ranges),
+ merge_b->implicit_src_gap,
+ child->remaining_ranges, FALSE,
+ pool));
+ }
+
+ if (revision1 > revision2) /* Reverse merge */
+ svn_rangelist_reverse(child->remaining_ranges, iterpool);
+ }
}
svn_pool_destroy(iterpool);
@@ -3693,109 +3929,51 @@ populate_remaining_ranges(apr_array_head
/*** Other Helper Functions ***/
+/* Helper for record_mergeinfo_for_dir_merge().
-/* Create mergeinfo describing the merge of RANGELIST into TARGET_WCPATH,
- accounting for paths unaffected by the merge due to skips or conflicts from
- NOTIFY_B. For 'immediates' merge it sets an inheritable mergeinfo
- corresponding to current merge on merge target. For 'files' merge it sets
- an inheritable mergeinfo corrsponding to current merge on merged files.
- If TARGET_WCPATH is a directory and it is missing an immediate child then
- TARGET_MISSING_CHILD should be true, otherwise it is false.*/
-static svn_error_t *
-determine_merges_performed(apr_hash_t **merges, const char *target_wcpath,
- apr_array_header_t *rangelist,
- svn_depth_t depth,
- svn_wc_adm_access_t *adm_access,
- notification_receiver_baton_t *notify_b,
- merge_cmd_baton_t *merge_b,
- apr_pool_t *pool)
-{
- apr_size_t nbr_skips = (notify_b->skipped_paths != NULL ?
- apr_hash_count(notify_b->skipped_paths) : 0);
-
- *merges = apr_hash_make(pool);
- apr_hash_set(*merges, target_wcpath, APR_HASH_KEY_STRING, rangelist);
- if (nbr_skips > 0)
- {
- apr_hash_index_t *hi;
-
- /* Override the mergeinfo for child paths which weren't
- actually merged. */
- for (hi = apr_hash_first(NULL, notify_b->skipped_paths); hi;
- hi = apr_hash_next(hi))
- {
- const void *skipped_path;
- svn_wc_status2_t *status;
-
- apr_hash_this(hi, &skipped_path, NULL, NULL);
-
- /* Before we override, make sure this is a versioned path, it
- might be an unversioned obstruction. */
- SVN_ERR(svn_wc_status2(&status, (const char *) skipped_path,
- adm_access, pool));
- if (status->text_status == svn_wc_status_none
- || status->text_status == svn_wc_status_unversioned)
- continue;
-
- /* Add an empty range list for this path.
-
- ### TODO: This works fine for a file path skipped because it is
- ### missing as long as the file's parent directory is present.
- ### But missing directory paths skipped are not handled yet,
- ### see issue #2915. */
- apr_hash_set(*merges, (const char *) skipped_path,
- APR_HASH_KEY_STRING,
- apr_array_make(pool, 0, sizeof(svn_merge_range_t)));
-
- if (nbr_skips < notify_b->nbr_notifications)
- /* ### Use RANGELIST as the mergeinfo for all children of
- ### this path which were not also explicitly
- ### skipped? */
- ;
- }
- }
- if ((depth != svn_depth_infinity) && notify_b->merged_paths)
- {
- apr_hash_index_t *hi;
- const void *merged_path;
-
- for (hi = apr_hash_first(NULL, notify_b->merged_paths); hi;
- hi = apr_hash_next(hi))
- {
- const svn_wc_entry_t *child_entry;
- apr_array_header_t *rangelist_of_child = NULL;
- apr_hash_this(hi, &merged_path, NULL, NULL);
- SVN_ERR(svn_wc__entry_versioned(&child_entry,
- merged_path,
- adm_access, FALSE,
- pool));
- if (((child_entry->kind == svn_node_dir)
- && (strcmp(merge_b->target, merged_path) == 0)
- && (depth == svn_depth_immediates))
- || ((child_entry->kind == svn_node_file)
- && (depth == svn_depth_files)))
- {
- /* Set the explicit inheritable mergeinfo for,
- 1. Merge target directory if depth is immediates.
- 2. If merge is on a file and requested depth is 'files'.
- */
- int i;
- rangelist_of_child = svn_rangelist_dup(rangelist, pool);
- for (i = 0; i < rangelist_of_child->nelts; i++)
- {
- svn_merge_range_t *rng =
- APR_ARRAY_IDX(rangelist_of_child, i, svn_merge_range_t *);
- rng->inheritable = TRUE;
- }
- }
- if (rangelist_of_child)
- {
- apr_hash_set(*merges, (const char *)merged_path,
- APR_HASH_KEY_STRING, rangelist_of_child);
- }
+ Adjust, in place, the inheritability of the ranges in RANGELIST to
+ describe a merge of RANGELIST into WC_WCPATH at depth DEPTH. ENTRY
+ is the entry for WC_PATH.
+
+ WC_PATH_IS_MERGE_TARGET is true if WC_PATH is the target of the merge,
+ otherwise WC_PATH is a subtree.
+
+ WC_PATH_HAS_MISSING_CHILD is true if WC_PATH is missing an immediate child
+ because the child is switched or absent from the WC, or due to a sparse
+ checkout -- see get_mergeinfo_paths(). */
+static svn_error_t *
+calculate_merge_inheritance(apr_array_header_t *rangelist,
+ const char *wc_path,
+ const svn_wc_entry_t *entry,
+ svn_boolean_t wc_path_is_merge_target,
+ svn_boolean_t wc_path_has_missing_child,
+ svn_depth_t depth)
+{
+ if (entry->kind == svn_node_file)
+ {
+ /* Files *never* have non-inheritable mergeinfo. */
+ svn_rangelist__set_inheritance(rangelist, TRUE);
+ }
+ else if (entry->kind == svn_node_dir)
+ {
+ if (wc_path_is_merge_target)
+ {
+ if (wc_path_has_missing_child
+ || depth == svn_depth_files
+ || depth == svn_depth_empty)
+ svn_rangelist__set_inheritance(rangelist, FALSE);
+ else /* depth == svn_depth_files || depth == svn_depth_empty */
+ svn_rangelist__set_inheritance(rangelist, TRUE);
+ }
+ else /* WC_PATH is a directory subtree of the target. */
+ {
+ if (wc_path_has_missing_child
+ || depth == svn_depth_immediates)
+ svn_rangelist__set_inheritance(rangelist, FALSE);
+ else /* depth == infinity */
+ svn_rangelist__set_inheritance(rangelist, TRUE);
}
}
-
return SVN_NO_ERROR;
}
@@ -3928,6 +4106,84 @@ update_wc_mergeinfo(const char *target_w
return SVN_NO_ERROR;
}
+/* Helper for record_mergeinfo_for_dir_merge().
+
+ Record override mergeinfo on any paths skipped during a merge.
+
+ Set empty mergeinfo on each path in NOTIFY_B->SKIPPED_PATHS so the path
+ does not incorrectly inherit mergeinfo that will later be describing
+ the merge.
+
+ MERGEINFO_PATH, ADM_ACCESS, NOTIFY_B, and MERGE_B are all cascased from
+ arguments of the same name in the caller.
+
+ TARGET_ENTRY is the entry from MERGE_B->TARGET. IS_ROLLBACK is true if
+ the caller is recording a reverse merge and false otherwise. RANGELIST
+ is the set of revisions being merged from MERGEINFO_PATH to
+ MERGE_B->TARGET. */
+static svn_error_t *
+record_skips(const char *mergeinfo_path,
+ const svn_wc_entry_t *target_entry,
+ apr_array_header_t *rangelist,
+ svn_boolean_t is_rollback,
+ svn_wc_adm_access_t *adm_access,
+ notification_receiver_baton_t *notify_b,
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *pool)
+{
+ apr_size_t nbr_skips = (notify_b->skipped_paths != NULL ?
+ apr_hash_count(notify_b->skipped_paths) : 0);
+
+ if (nbr_skips > 0)
+ {
+ apr_hash_index_t *hi;
+ apr_hash_t *merges = apr_hash_make(pool);
+
+ /* Override the mergeinfo for child paths which weren't
+ actually merged. */
+ for (hi = apr_hash_first(NULL, notify_b->skipped_paths); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *skipped_path;
+ svn_wc_status2_t *status;
+
+ apr_hash_this(hi, &skipped_path, NULL, NULL);
+
+ /* Before we override, make sure this is a versioned path, it
+ might be an unversioned obstruction. */
+ SVN_ERR(svn_wc_status2(&status, skipped_path, adm_access, pool));
+ if (status->text_status == svn_wc_status_none
+ || status->text_status == svn_wc_status_unversioned)
+ continue;
+
+ /* Add an empty range list for this path.
+
+ ### TODO: This works fine for a file path skipped because it is
+ ### missing as long as the file's parent directory is present.
+ ### But missing directory paths skipped are not handled yet,
+ ### see issue #2915.
+
+ ### TODO: An empty range is fine if the skipped path doesn't
+ ### inherit any mergeinfo from a parent, but if it does
+ ### we need to account for that. See issue #3440
+ ### http://subversion.tigris.org/issues/show_bug.cgi?id=3440. */
+ apr_hash_set(merges, (const char *) skipped_path,
+ APR_HASH_KEY_STRING,
+ apr_array_make(pool, 0, sizeof(svn_merge_range_t)));
+
+ if (nbr_skips < notify_b->nbr_notifications)
+ /* ### Use RANGELIST as the mergeinfo for all children of
+ ### this path which were not also explicitly
+ ### skipped? */
+ ;
+ }
+ SVN_ERR(update_wc_mergeinfo(merge_b->target, target_entry,
+ mergeinfo_path, merges,
+ is_rollback, adm_access,
+ merge_b->ctx, pool));
+ }
+ return SVN_NO_ERROR;
+}
/* Create and return an error structure appropriate for the unmerged
revisions range(s). */
@@ -4590,6 +4846,9 @@ struct get_mergeinfo_walk_baton
/* RA session and client context cascaded from do_directory_merge() */
svn_ra_session_t *ra_session;
svn_client_ctx_t *ctx;
+
+ /* Pool from which to allocate new elements of CHILDREN_WITH_MERGEINFO. */
+ apr_pool_t *pool;
};
@@ -4597,12 +4856,13 @@ struct get_mergeinfo_walk_baton
Given PATH, its corresponding ENTRY, and WB, where WB is the WALK_BATON
of type "struct get_mergeinfo_walk_baton *": If PATH is switched,
- has explicit working svn:mergeinfo from a corresponding merge source, is
- missing a child due to a sparse checkout, is absent from disk, is
- scheduled for deletion, or if the walk is being done as part of a reverse
- merge, then create a svn_client__merge_path_t *representing *PATH,
- allocated in WB->CHILDREN_WITH_MERGEINFO->POOL, and push it onto the
- WB->CHILDREN_WITH_MERGEINFO array. */
+ has explicit working svn:mergeinfo from the corresponding merge source and
+ this walk is being done as part of a forward merge or has *any* explicit
+ working svn:mergeinfo and this walk is being done as part of a reverse
+ merge, is missing a child due to a sparse checkout, is absent from disk,
+ is scheduled for deletion, then create a svn_client__merge_path_t *
+ representing *PATH, allocated in BATON->POOL, and
+ push it onto the WB->CHILDREN_WITH_MERGEINFO array. */
static svn_error_t *
get_mergeinfo_walk_cb(const char *path,
const svn_wc_entry_t *entry,
@@ -4776,8 +5036,8 @@ get_mergeinfo_walk_cb(const char *path,
)
{
svn_client__merge_path_t *child =
- apr_pcalloc(wb->children_with_mergeinfo->pool, sizeof(*child));
- child->path = apr_pstrdup(wb->children_with_mergeinfo->pool, path);
+ apr_pcalloc(wb->pool, sizeof(*child));
+ child->path = apr_pstrdup(wb->pool, path);
child->missing_child = (entry->depth == svn_depth_empty
|| entry->depth == svn_depth_files
|| ((wb->depth == svn_depth_immediates) &&
@@ -4875,12 +5135,13 @@ get_child_with_mergeinfo(const apr_array
}
/* Insert a deep copy of INSERT_ELEMENT into the CHILDREN_WITH_MERGEINFO
- array at its correct position. Allocate the new storage from the array's
- pool. CHILDREN_WITH_MERGEINFO is a depth first sorted array of
+ array at its correct position. Allocate the new storage in POOL.
+ CHILDREN_WITH_MERGEINFO is a depth first sorted array of
(svn_client__merge_path_t *). */
static void
insert_child_to_merge(apr_array_header_t *children_with_mergeinfo,
- const svn_client__merge_path_t *insert_element)
+ const svn_client__merge_path_t *insert_element,
+ apr_pool_t *pool)
{
int insert_index;
const svn_client__merge_path_t *new_element;
@@ -4890,8 +5151,7 @@ insert_child_to_merge(apr_array_header_t
svn_sort__bsearch_lower_bound(&insert_element, children_with_mergeinfo,
compare_merge_path_t_as_paths);
- new_element = svn_client__merge_path_dup(insert_element,
- children_with_mergeinfo->pool);
+ new_element = svn_client__merge_path_dup(insert_element, pool);
svn_sort__array_insert(&new_element, children_with_mergeinfo, insert_index);
}
@@ -4913,7 +5173,7 @@ insert_child_to_merge(apr_array_header_t
already present in CHILDREN_WITH_MERGEINFO are also added to the array,
limited by DEPTH (e.g. don't add directory siblings of a switched file).
Use POOL for temporary allocations only, any new CHILDREN_WITH_MERGEINFO
- elements are allocated in CHILDREN_WITH_MERGEINFO->POOL. */
+ elements are allocated in POOL. */
static svn_error_t *
insert_parent_and_sibs_of_sw_absent_del_entry(
apr_array_header_t *children_with_mergeinfo,
@@ -4943,11 +5203,11 @@ insert_parent_and_sibs_of_sw_absent_del_
else
{
/* Create a new element to insert into CHILDREN_WITH_MERGEINFO. */
- parent = apr_pcalloc(children_with_mergeinfo->pool, sizeof(*parent));
- parent->path = apr_pstrdup(children_with_mergeinfo->pool, parent_path);
+ parent = apr_pcalloc(pool, sizeof(*parent));
+ parent->path = apr_pstrdup(pool, parent_path);
parent->missing_child = TRUE;
/* Insert PARENT into CHILDREN_WITH_MERGEINFO. */
- insert_child_to_merge(children_with_mergeinfo, parent);
+ insert_child_to_merge(children_with_mergeinfo, parent, pool);
/* Increment for loop index so we don't process the inserted element. */
(*curr_index)++;
} /*(parent == NULL) */
@@ -4985,11 +5245,12 @@ insert_parent_and_sibs_of_sw_absent_del_
continue;
}
- sibling_of_missing = apr_pcalloc(children_with_mergeinfo->pool,
+ sibling_of_missing = apr_pcalloc(pool,
sizeof(*sibling_of_missing));
- sibling_of_missing->path = apr_pstrdup(children_with_mergeinfo->pool,
+ sibling_of_missing->path = apr_pstrdup(pool,
child_path);
- insert_child_to_merge(children_with_mergeinfo, sibling_of_missing);
+ insert_child_to_merge(children_with_mergeinfo, sibling_of_missing,
+ pool);
}
}
return SVN_NO_ERROR;
@@ -5002,8 +5263,9 @@ insert_parent_and_sibs_of_sw_absent_del_
Create an svn_client__merge_path_t * for any path which meets one or more
of the following criteria:
- 1) Path has working svn:mergeinfo from corresponding merge source or
- has empty mergeinfo.
+ 1) do_directory_merge() is processing a forward merge and path has
+ working svn:mergeinfo from corresponding merge source or has empty
+ mergeinfo.
2) Path is switched.
3) Path is a subtree of the merge target (i.e. is not equal to
MERGE_CMD_BATON->TARGET) and has no mergeinfo of its own but its
@@ -5023,7 +5285,8 @@ insert_parent_and_sibs_of_sw_absent_del_
DEPTH is svn_depth_immediates.
9) Path is an immediate *file* child of MERGE_CMD_BATON->TARGET and
DEPTH is svn_depth_files.
- 10) do_directory_merge() is processing a reverse merge.
+ 10) do_directory_merge() is processing a reverse merge and path has any
+ working svn:mergeinfo.
If HONOR_MERGEINFO is FALSE, then create an svn_client__merge_path_t * only
for MERGE_CMD_BATON->TARGET (i.e. only criteria 7 is applied).
@@ -5082,6 +5345,7 @@ get_mergeinfo_paths(apr_array_header_t *
wb.depth = depth;
wb.ra_session = ra_session;
wb.ctx = merge_cmd_baton->ctx;
+ wb.pool = pool;
/* Cover cases 1), 2), and 6), 7), 8), 9), and 10) by walking the WC to get
all paths which have mergeinfo and/or are switched or are absent from
@@ -5201,13 +5465,14 @@ get_mergeinfo_paths(apr_array_header_t *
directory and file children. */
child_of_noninheritable =
- apr_pcalloc(children_with_mergeinfo->pool,
+ apr_pcalloc(pool,
sizeof(*child_of_noninheritable));
child_of_noninheritable->path =
- apr_pstrdup(children_with_mergeinfo->pool,
+ apr_pstrdup(pool,
child_path);
insert_child_to_merge(children_with_mergeinfo,
- child_of_noninheritable);
+ child_of_noninheritable,
+ pool);
if (!merge_cmd_baton->dry_run
&& merge_cmd_baton->same_repos)
{
@@ -5236,7 +5501,7 @@ get_mergeinfo_paths(apr_array_header_t *
/* Case 4 and 5 are handled by the following function. */
SVN_ERR(insert_parent_and_sibs_of_sw_absent_del_entry(
children_with_mergeinfo, merge_cmd_baton, &i, child,
- depth, adm_access, iterpool));
+ depth, adm_access, pool));
} /* i < children_with_mergeinfo->nelts */
svn_pool_destroy(iterpool);
} /* honor_mergeinfo */
@@ -5909,7 +6174,7 @@ do_file_merge(const char *url1,
source_root_url,
url1, revision1, url2, revision2,
target_mergeinfo,
- FALSE,
+ merge_b->implicit_src_gap, FALSE,
FALSE, merge_b->ra_session1,
entry, adm_access, ctx, pool));
remaining_ranges = merge_target->remaining_ranges;
@@ -6105,7 +6370,6 @@ do_file_merge(const char *url1,
merge ranges, include the noop ones. */
if (record_mergeinfo && remaining_ranges->nelts)
{
- apr_hash_t *merges;
apr_array_header_t *filtered_rangelist;
/* Filter any ranges from TARGET_WCPATH's own history, there is no
@@ -6117,13 +6381,15 @@ do_file_merge(const char *url1,
merge_target->implicit_mergeinfo,
&range, subpool));
- if (filtered_rangelist->nelts)
+ /* Only record mergeinfo if there is something other than
+ self-referential mergeinfo, but don't record mergeinfo if
+ TARGET_WCPATH was skipped. */
+ if (filtered_rangelist->nelts
+ && (!notify_b->skipped_paths
+ || (apr_hash_count(notify_b->skipped_paths) == 0)))
{
- SVN_ERR(determine_merges_performed(&merges, target_wcpath,
- filtered_rangelist,
- svn_depth_infinity,
- adm_access, notify_b,
- merge_b, subpool));
+ apr_hash_t *merges = apr_hash_make(subpool);
+
/* If merge target has indirect mergeinfo set it before
recording the first merge range. */
if (indirect)
@@ -6131,6 +6397,8 @@ do_file_merge(const char *url1,
target_mergeinfo,
adm_access, subpool));
+ apr_hash_set(merges, target_wcpath, APR_HASH_KEY_STRING,
+ filtered_rangelist);
SVN_ERR(update_wc_mergeinfo(target_wcpath, entry, mergeinfo_path,
merges, is_rollback, adm_access,
ctx, subpool));
@@ -6172,10 +6440,8 @@ do_file_merge(const char *url1,
latter is set to a rangelist equal to the remaining_ranges of the path's
nearest path-wise ancestor in NOTIFY_B->CHILDREN_WITH_MERGEINFO.
- POOL is used only for temporary allocations, any elements added to
- NOTIFY_B->CHILDREN_WITH_MERGEINFO are allocated in
- NOTIFY_B->CHILDREN_WITH_MERGEINFO->POOL.
- */
+ Any elements added to NOTIFY_B->CHILDREN_WITH_MERGEINFO are allocated
+ in POOL. */
static svn_error_t *
process_children_with_new_mergeinfo(merge_cmd_baton_t *merge_b,
notification_receiver_baton_t *notify_b,
@@ -6268,12 +6534,9 @@ process_children_with_new_mergeinfo(merg
APR_ARRAY_IDX(notify_b->children_with_mergeinfo,
parent_index,
svn_client__merge_path_t *);
- new_child =
- apr_pcalloc(notify_b->children_with_mergeinfo->pool,
- sizeof(*new_child));
- new_child->path =
- apr_pstrdup(notify_b->children_with_mergeinfo->pool,
- path_with_new_mergeinfo);
+ new_child = apr_pcalloc(pool, sizeof(*new_child));
+ new_child->path = apr_pstrdup(pool,
+ path_with_new_mergeinfo);
/* If path_with_new_mergeinfo is the merge target itself
then it should already be in
@@ -6288,10 +6551,9 @@ process_children_with_new_mergeinfo(merg
/* Set the path's remaining_ranges equal to its parent's. */
new_child->remaining_ranges = svn_rangelist_dup(
- parent->remaining_ranges,
- notify_b->children_with_mergeinfo->pool);
+ parent->remaining_ranges, pool);
insert_child_to_merge(notify_b->children_with_mergeinfo,
- new_child);
+ new_child, pool);
}
}
/* Return MERGE_B->RA_SESSION2 to its initial state if we
@@ -6305,6 +6567,345 @@ process_children_with_new_mergeinfo(merg
return SVN_NO_ERROR;
}
+/* Helper for do_directory_merge() when performing mergeinfo unaware merges.
+
+ Merge the diff of URL1@REVISION1:URL2@REVISION2 to TARGET_DIR_WCPATH.
+
+ URL1, REVISION1, URL2, REVISION2, ADM_ACCESS, DEPTH, NOTIFY_B, and MERGE_B
+ are all cascaded from do_directory_merge's arguments of the same names.
+
+ NOTE: This is a very thin wrapper around drive_merge_report_editor() and
+ exists only to populate NOTIFY_B->CHILREN_WITH_MERGEINFO with the single
+ element expected during mergeinfo unaware merges..
+*/
+static svn_error_t *
+do_mergeinfo_unaware_dir_merge(const char *url1,
+ svn_revnum_t revision1,
+ const char *url2,
+ svn_revnum_t revision2,
+ const char *target_dir_wcpath,
+ svn_wc_adm_access_t *adm_access,
+ svn_depth_t depth,
+ notification_receiver_baton_t *notify_b,
+ merge_cmd_baton_t *merge_b,
+ apr_pool_t *pool)
+{
+ /* Initialize NOTIFY_B->CHILDREN_WITH_MERGEINFO and populate it with
+ one element describing the merge of REVISION1:REVISION2 to
+ TARGET_DIR_WCPATH. */
+ svn_client__merge_path_t *item = apr_pcalloc(pool, sizeof(*item));
+ svn_merge_range_t *itemrange = apr_pcalloc(pool, sizeof(*itemrange));
+ apr_array_header_t *remaining_ranges = apr_array_make(
+ pool, 1, sizeof(svn_merge_range_t *));
+
+ itemrange->start = revision1;
+ itemrange->end = revision2;
+ itemrange->inheritable = TRUE;
+
+ APR_ARRAY_PUSH(remaining_ranges, svn_merge_range_t *) = itemrange;
+
+ item->path = apr_pstrdup(pool, target_dir_wcpath);
+ item->remaining_ranges = remaining_ranges;
+ APR_ARRAY_PUSH(notify_b->children_with_mergeinfo,
+ svn_client__merge_path_t *) = item;
+ return drive_merge_report_editor(target_dir_wcpath,
+ url1, revision1, url2, revision2,
+ NULL, depth, notify_b,
+ adm_access, &merge_callbacks,
+ merge_b, pool);
+}
+
+/* Helper for do_directory_merge().
+
+ Record mergeinfo describing a merge of MERGED_RANGE->START:
+ MERGED_RANGE->END from the repository relative path MERGEINFO_PATH to
+ the directory represented by TARGET_ENTRY. Obviously this should only
+ be called if recording mergeinfo -- see doc string for
+ mergeinfo_behavior().
+
+ TARGET_ENTRY, DEPTH, NOTIFY_B, MERGE_B, and ADM_ACCESS are all cascaded
+ from do_directory_merge's arguments of the same names.
+*/
+static svn_error_t *
+record_mergeinfo_for_dir_merge(const svn_wc_entry_t *target_entry,
+ svn_merge_range_t *merged_range,
+ const char *mergeinfo_path,
+ svn_depth_t depth,
+ notification_receiver_baton_t *notify_b,
+ merge_cmd_baton_t *merge_b,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ int i;
+ int merge_target_len = strlen(merge_b->target);
+ svn_boolean_t is_rollback = (merged_range->start > merged_range->end);
+
+ /* Update the WC mergeinfo here to account for our new
+ merges, minus any unresolved conflicts and skips. */
+
+ /* We need a scratch pool for iterations below. */
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ /* Remove absent children at or under TARGET_WCPATH from
+ NOTIFY_B->SKIPPED_PATHS and NOTIFY_B->CHILDREN_WITH_MERGEINFO
+ before we calculate the merges performed. */
+ remove_absent_children(merge_b->target,
+ notify_b->children_with_mergeinfo, notify_b);
+
+ for (i = 0; i < notify_b->children_with_mergeinfo->nelts; i++)
+ {
+ const char *child_repos_path;
+ const char *child_merge_src_canon_path;
+ const svn_wc_entry_t *child_entry;
+ apr_array_header_t *child_merge_rangelist;
+ apr_hash_t *child_merges;
+ svn_client__merge_path_t *child =
+ APR_ARRAY_IDX(notify_b->children_with_mergeinfo, i,
+ svn_client__merge_path_t *);
+ SVN_ERR_ASSERT(child);
+ if (child->absent)
+ continue;
+
+ svn_pool_clear(iterpool);
+
+ if (strlen(child->path) == merge_target_len)
+ child_repos_path = "";
+ else
+ child_repos_path = child->path +
+ (merge_target_len ? merge_target_len + 1 : 0);
+ child_merge_src_canon_path = svn_path_join(mergeinfo_path,
+ child_repos_path,
+ iterpool);
+ SVN_ERR(svn_wc__entry_versioned(&child_entry, child->path,
+ adm_access, FALSE, iterpool));
+
+ /* Filter any ranges from each child's natural history before
+ setting mergeinfo describing the merge. */
+ SVN_ERR(filter_natural_history_from_mergeinfo(
+ &child_merge_rangelist, child_merge_src_canon_path,
+ child->implicit_mergeinfo, merged_range, iterpool));
+
+ if (child_merge_rangelist->nelts == 0)
+ continue;
+
+ /* If we are here we know we will be recording some mergeinfo, but
+ before we do set override mergeinfo on skipped paths so they
+ don't incorrectly inherit the mergeinfo we are about to set.
+ We only need to do this once. */
+ if (i == 0)
+ SVN_ERR(record_skips(mergeinfo_path,
+ target_entry, child_merge_rangelist,
+ is_rollback, adm_access,
+ notify_b, merge_b, iterpool));
+
+ SVN_ERR(calculate_merge_inheritance(child_merge_rangelist, child->path,
+ child_entry, i == 0,
+ child->missing_child,
+ depth));
+
+ /* If CHILD has indirect mergeinfo set it before
+ recording the first merge range. */
+ if (child->indirect_mergeinfo)
+ SVN_ERR(svn_client__record_wc_mergeinfo(child->path,
+ child->pre_merge_mergeinfo,
+ adm_access,
+ iterpool));
+ if (merge_b->implicit_src_gap)
+ {
+ /* If this is a reverse merge reorder CHILD->REMAINING_RANGES
+ so it will work with the svn_rangelist_remove API. */
+ if (is_rollback)
+ SVN_ERR(svn_rangelist_reverse(child_merge_rangelist, iterpool));
+
+ SVN_ERR(svn_rangelist_remove(&child_merge_rangelist,
+ merge_b->implicit_src_gap,
+ child_merge_rangelist, FALSE,
+ iterpool));
+ if (is_rollback)
+ SVN_ERR(svn_rangelist_reverse(child_merge_rangelist, iterpool));
+ }
+
+ child_merges = apr_hash_make(iterpool);
+ apr_hash_set(child_merges, child->path, APR_HASH_KEY_STRING,
+ child_merge_rangelist);
+ SVN_ERR(update_wc_mergeinfo(child->path, child_entry,
+ child_merge_src_canon_path,
+ child_merges, is_rollback,
+ adm_access, merge_b->ctx, iterpool));
+
+ /* Elide explicit subtree mergeinfo. */
+ if (i > 0)
+ {
+ svn_boolean_t in_switched_subtree = FALSE;
+
+ if (child->switched)
+ in_switched_subtree = TRUE;
+ else if (i > 1)
+ {
+ /* Check if CHILD is part of a switched subtree */
+ svn_client__merge_path_t *parent;
+ int j = i - 1;
+ for (; j > 0; j--)
+ {
+ parent = APR_ARRAY_IDX(notify_b->children_with_mergeinfo,
+ j, svn_client__merge_path_t *);
+ if (parent
+ && parent->switched
+ && svn_path_is_ancestor(parent->path, child->path))
+ {
+ in_switched_subtree = TRUE;
+ break;
+ }
+ }
+ }
+
+ /* Allow mergeinfo on switched subtrees to elide to the
+ repository. Otherwise limit elision to the merge target
+ for now. do_directory_merge() will eventually try to
+ elide that when the merge is complete. */
+ SVN_ERR(svn_client__elide_mergeinfo(
+ child->path,
+ in_switched_subtree ? NULL : merge_b->target,
+ child_entry, adm_access, merge_b->ctx, iterpool));
+ }
+ } /* (i = 0; i < notify_b->children_with_mergeinfo->nelts; i++) */
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+/* Helper for do_directory_merge().
+
+ Similar to record_mergeinfo_for_dir_merge in that it records mergeinfo
+ describing a merge of MERGED_RANGE->START:MERGED_RANGE->END from the
+ repository relative path MERGEINFO_PATH to the directory represented by
+ TARGET_ENTRY. Unlike record_mergeinfo_for_dir_merge() though, this
+ funtion only records mergeinfo on *new* subtrees added by the merge which
+ are children of paths with non-inheritable ranges or which have missing
+ siblings - see criteria 3 and 5 in the doc string for get_mergeinfo_paths.
+ See also issue #2829
+ http://subversion.tigris.org/issues/show_bug.cgi?id=2829#desc14.
+
+ DEPTH, NOTIFY_B, MERGE_B, and ADM_ACCESS are all cascaded from
+ do_directory_merge's arguments of the same names.
+*/
+static svn_error_t *
+record_mergeinfo_for_added_subtrees(svn_merge_range_t *merged_range,
+ const char *mergeinfo_path,
+ svn_depth_t depth,
+ notification_receiver_baton_t *notify_b,
+ merge_cmd_baton_t *merge_b,
+ svn_wc_adm_access_t *adm_access,
+ apr_pool_t *pool)
+{
+ /* If no paths were added by the merge then we have nothing to do. */
+ if (notify_b->added_paths)
+ {
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(NULL, notify_b->added_paths); hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ const char *added_path;
+ const svn_string_t *added_path_parent_propval;
+
+ apr_hash_this(hi, &key, NULL, NULL);
+ added_path = key;
+
+ apr_pool_clear(iterpool);
+
+ /* Rather than using svn_client__get_wc_mergeinfo() and
+ analyzing the mergeinfo it returns to determine if
+ ADDED_PATH's parent has non-inheritable mergeinfo, it is
+ much simpler to just get the svn_string_t representation
+ of the svn:mergeinfo prop and look for the '*'
+ non-inheritable marker. */
+ SVN_ERR(svn_wc_prop_get(&added_path_parent_propval,
+ SVN_PROP_MERGEINFO,
+ svn_dirent_dirname(added_path, iterpool),
+ adm_access, iterpool));
+ if (added_path_parent_propval
+ && strstr(added_path_parent_propval->data,
+ SVN_MERGEINFO_NONINHERITABLE_STR))
+ {
+ /* ADDED_PATH's immediate parent has non-inheritable
+ mergeinfo. */
+ svn_client__merge_path_t *target_merge_path =
+ APR_ARRAY_IDX(notify_b->children_with_mergeinfo, 0,
+ svn_client__merge_path_t *);
+ svn_boolean_t inherited;
+ svn_merge_range_t *rng;
+ svn_mergeinfo_t merge_mergeinfo, added_path_mergeinfo;
+ apr_array_header_t *rangelist;
+ const svn_wc_entry_t *entry;
+ const char *rel_added_path,
+ *abs_added_path, *abs_target_path,
+ *added_path_mergeinfo_path;
+
+ SVN_ERR(svn_wc__entry_versioned(&entry, added_path,
+ adm_access, FALSE,
+ iterpool));
+
+ /* Calculate the mergeinfo resulting from this merge. */
+ merge_mergeinfo = apr_hash_make(iterpool);
+ rangelist = apr_array_make(iterpool, 1,
+ sizeof(svn_merge_range_t *));
+ rng = svn_merge_range_dup(merged_range, iterpool);
+ if (entry->kind == svn_node_file)
+ rng->inheritable = TRUE;
+ else
+ rng->inheritable =
+ (!(depth == svn_depth_infinity
+ || depth == svn_depth_immediates));
+ APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = rng;
+
+ /* Create the new mergeinfo path for
+ added_path's mergeinfo. */
+ SVN_ERR(svn_path_get_absolute(&abs_target_path,
+ target_merge_path->path,
+ iterpool));
+ SVN_ERR(svn_path_get_absolute(&abs_added_path,
+ added_path,
+ iterpool));
+
+ /* abs_added_path had better be a child of abs_target_path
+ or something is *really* wrong. */
+
+ rel_added_path = svn_path_is_child(abs_target_path,
+ abs_added_path,
+ iterpool);
+ SVN_ERR_ASSERT(rel_added_path);
+ added_path_mergeinfo_path = svn_path_join(mergeinfo_path,
+ rel_added_path,
+ iterpool);
+ apr_hash_set(merge_mergeinfo,
+ added_path_mergeinfo_path,
+ APR_HASH_KEY_STRING, rangelist);
+
+ /* Get any explicit mergeinfo the added path has. */
+ SVN_ERR(svn_client__get_wc_mergeinfo(
+ &added_path_mergeinfo, &inherited, FALSE,
+ svn_mergeinfo_explicit, entry, added_path,
+ NULL, NULL, adm_access, merge_b->ctx, iterpool));
+
+ /* Combine the explict mergeinfo on the added path (if any)
+ with the mergeinfo for this merge. */
+ if (added_path_mergeinfo)
+ SVN_ERR(svn_mergeinfo_merge(merge_mergeinfo,
+ added_path_mergeinfo,
+ iterpool));
+ SVN_ERR(svn_client__record_wc_mergeinfo(added_path,
+ merge_mergeinfo,
+ adm_access,
+ iterpool));
+ }
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
/* Helper for do_merge() when the merge target is a directory.
Perform a merge of changes between URL1@REVISION1 and URL2@REVISION2
@@ -6339,12 +6940,8 @@ do_directory_merge(const char *url1,
apr_pool_t *pool)
{
svn_error_t *err = SVN_NO_ERROR;
- int merge_target_len = strlen(merge_b->target);
- int i;
svn_merge_range_t range;
svn_ra_session_t *ra_session;
- svn_boolean_t inheritable;
- apr_pool_t *iterpool;
const char *target_wcpath = svn_wc_adm_access_path(adm_access);
svn_client__merge_path_t *target_merge_path;
svn_boolean_t is_rollback = (revision1 > revision2);
@@ -6360,36 +6957,12 @@ do_directory_merge(const char *url1,
notify_b->children_with_mergeinfo =
apr_array_make(pool, 0, sizeof(svn_client__merge_path_t *));
- /* If our merge sources aren't related to each other, or don't come
- from the same repository as our target, mergeinfo is meaningless
- and we can skip right to the business of merging changes! We'll
- just drop a dummy item into NOTIFY_B->CHILDREN_WITH_MERGEINFO if
- the merge sources are related. */
- if (! (merge_b->sources_ancestral && merge_b->same_repos))
- {
- if (merge_b->sources_ancestral)
- {
- svn_client__merge_path_t *item = apr_pcalloc(pool, sizeof(*item));
- svn_merge_range_t *itemrange = apr_pcalloc(pool, sizeof(*itemrange));
- apr_array_header_t *remaining_ranges =
- apr_array_make(pool, 1, sizeof(svn_merge_range_t *));
-
- itemrange->start = revision1;
- itemrange->end = revision2;
- itemrange->inheritable = TRUE;
- APR_ARRAY_PUSH(remaining_ranges, svn_merge_range_t *) = itemrange;
-
- item->path = apr_pstrdup(pool, target_wcpath);
- item->remaining_ranges = remaining_ranges;
- APR_ARRAY_PUSH(notify_b->children_with_mergeinfo,
- svn_client__merge_path_t *) = item;
- }
- return drive_merge_report_editor(target_wcpath,
- url1, revision1, url2, revision2,
- NULL, depth, notify_b,
- adm_access, &merge_callbacks,
- merge_b, pool);
- }
+ /* If we are not honoring mergeinfo we can skip right to the
+ business of merging changes! */
+ if (!honor_mergeinfo)
+ return do_mergeinfo_unaware_dir_merge(url1, revision1, url2, revision2,
+ target_wcpath, adm_access, depth,
+ notify_b, merge_b, pool);
/*** If we get here, we're dealing with related sources from the
same repository as the target -- merge tracking might be
@@ -6418,9 +6991,6 @@ do_directory_merge(const char *url1,
target_merge_path = APR_ARRAY_IDX(notify_b->children_with_mergeinfo, 0,
svn_client__merge_path_t *);
merge_b->target_missing_child = target_merge_path->missing_child;
- inheritable = ((! merge_b->target_missing_child)
- && ((depth == svn_depth_infinity)
- || (depth == svn_depth_immediates)));
/* If we are honoring mergeinfo, then for each item in
NOTIFY_B->CHILDREN_WITH_MERGEINFO, we need to calculate what needs to be
@@ -6429,15 +6999,15 @@ do_directory_merge(const char *url1,
SVN_ERR(populate_remaining_ranges(notify_b->children_with_mergeinfo,
source_root_url,
url1, revision1, url2, revision2,
- inheritable, honor_mergeinfo,
+ honor_mergeinfo,
ra_session, mergeinfo_path,
- adm_access, merge_b));
+ adm_access, merge_b, pool));
/* Always start with a range which describes the most inclusive merge
possible, i.e. REVISION1:REVISION2. */
range.start = revision1;
range.end = revision2;
- range.inheritable = inheritable;
+ range.inheritable = TRUE;
if (honor_mergeinfo && !merge_b->record_only)
{
@@ -6455,6 +7025,8 @@ do_directory_merge(const char *url1,
/* Is there anything to merge? */
if (SVN_IS_VALID_REVNUM(start_rev))
{
+ apr_pool_t *iterpool;
+
/* Adjust range to describe the start of our most
inclusive merge. */
range.start = start_rev;
@@ -6563,8 +7135,7 @@ do_directory_merge(const char *url1,
inherited is recorded and then add these paths to
MERGE_B->CHILDREN_WITH_MERGEINFO.*/
SVN_ERR(process_children_with_new_mergeinfo(merge_b, notify_b,
- adm_access,
- iterpool));
+ adm_access, pool));
/* If any subtrees had their explicit mergeinfo deleted as a
result of the merge then remove these paths from
@@ -6618,149 +7189,16 @@ do_directory_merge(const char *url1,
}
/* Record mergeinfo where appropriate.*/
- iterpool = svn_pool_create(pool);
if (record_mergeinfo)
{
- apr_array_header_t *filtered_rangelist;
- svn_client__merge_path_t *merge_target =
- APR_ARRAY_IDX(notify_b->children_with_mergeinfo, 0,
- svn_client__merge_path_t *);
-
- /* Update the WC mergeinfo here to account for our new
- merges, minus any unresolved conflicts and skips. */
- apr_hash_t *merges;
-
- /* Remove absent children at or under TARGET_WCPATH from
- NOTIFY_B->SKIPPED_PATHS and NOTIFY_B->CHILDREN_WITH_MERGEINFO
- before we calculate the merges performed. */
- remove_absent_children(merge_b->target,
- notify_b->children_with_mergeinfo, notify_b);
-
- /* Filter any ranges from MERGE_B->TARGET's own history, there is no
- need to record this explicitly in mergeinfo, it is already part of
- MERGE_B->TARGET's natural history (implicit mergeinfo). */
- SVN_ERR(filter_natural_history_from_mergeinfo(
- &filtered_rangelist, mergeinfo_path, merge_target->implicit_mergeinfo,
- &range, iterpool));
-
- if (filtered_rangelist->nelts)
- {
- SVN_ERR(determine_merges_performed(&merges, merge_b->target,
- filtered_rangelist, depth,
- adm_access, notify_b,
- merge_b, iterpool));
- SVN_ERR(update_wc_mergeinfo(merge_b->target, target_entry,
- mergeinfo_path, merges,
- is_rollback, adm_access, merge_b->ctx,
- iterpool));
- }
-
- for (i = 0; i < notify_b->children_with_mergeinfo->nelts; i++)
- {
- const char *child_repos_path;
- const char *child_merge_src_canon_path;
- const svn_wc_entry_t *child_entry;
- apr_array_header_t *child_merge_rangelist;
- apr_hash_t *child_merges;
- svn_client__merge_path_t *child =
- APR_ARRAY_IDX(notify_b->children_with_mergeinfo, i,
- svn_client__merge_path_t *);
- SVN_ERR_ASSERT(child);
- if (child->absent)
- continue;
-
- svn_pool_clear(iterpool);
-
- if (strlen(child->path) == merge_target_len)
- child_repos_path = "";
- else
- child_repos_path = child->path +
- (merge_target_len ? merge_target_len + 1 : 0);
- child_merge_src_canon_path = svn_path_join(mergeinfo_path,
- child_repos_path,
- iterpool);
- SVN_ERR(svn_wc__entry_versioned(&child_entry, child->path,
- adm_access, FALSE, iterpool));
-
- child_merges = apr_hash_make(iterpool);
-
- /* As we did above for the merge target, filter any ranges from
- each child's natural history before setting mergeinfo. */
- SVN_ERR(filter_natural_history_from_mergeinfo(
- &child_merge_rangelist, child_merge_src_canon_path,
- child->implicit_mergeinfo, &range, iterpool));
-
- if (child_merge_rangelist->nelts == 0)
- continue;
- else
- {
- int j;
- for (j = 0; j < child_merge_rangelist->nelts; j++)
- {
- svn_merge_range_t *rng =
- APR_ARRAY_IDX(child_merge_rangelist, j,
- svn_merge_range_t *);
- if (child_entry->kind == svn_node_file)
- rng->inheritable = TRUE;
- else
- rng->inheritable = (!(child->missing_child)
- && (depth == svn_depth_infinity
- || depth == svn_depth_immediates));
- }
- }
- apr_hash_set(child_merges, child->path, APR_HASH_KEY_STRING,
- child_merge_rangelist);
- /* If merge target has indirect mergeinfo set it before
- recording the first merge range. */
- if (child->indirect_mergeinfo)
- {
- SVN_ERR(svn_client__record_wc_mergeinfo(
- child->path,
- child->pre_merge_mergeinfo,
- adm_access,
- iterpool));
- }
- SVN_ERR(update_wc_mergeinfo(child->path, child_entry,
- child_merge_src_canon_path,
- child_merges, is_rollback,
- adm_access, merge_b->ctx, iterpool));
-
- /* Elide explicit subtree mergeinfo. */
- if (i > 0)
- {
- svn_boolean_t in_switched_subtree = FALSE;
-
- if (child->switched)
- in_switched_subtree = TRUE;
- else if (i > 1)
- {
- /* Check if CHILD is part of a switched subtree */
- svn_client__merge_path_t *parent;
- int j = i - 1;
- for (; j > 0; j--)
- {
- parent = APR_ARRAY_IDX(notify_b->children_with_mergeinfo,
- j, svn_client__merge_path_t *);
- if (parent
- && parent->switched
- && svn_path_is_ancestor(parent->path, child->path))
- {
- in_switched_subtree = TRUE;
- break;
- }
- }
- }
-
- /* Allow mergeinfo on switched subtrees to elide to the
- repository. Otherwise limit elision to the merge target
- for now. do_directory_merge() will eventually try to
- elide that when the merge is complete. */
- SVN_ERR(svn_client__elide_mergeinfo(
- child->path,
- in_switched_subtree ? NULL : merge_b->target,
- child_entry, adm_access, merge_b->ctx, iterpool));
- }
- } /* (i = 0; i < notify_b->children_with_mergeinfo->nelts; i++) */
+ SVN_ERR(record_mergeinfo_for_dir_merge(target_entry,
+ &range,
+ mergeinfo_path,
+ depth,
+ notify_b,
+ merge_b,
+ adm_access,
+ pool));
/* If a path has an immediate parent with non-inheritable mergeinfo at
this point, then it meets criteria 3 or 5 described in
@@ -6776,109 +7214,15 @@ do_directory_merge(const char *url1,
So here we look at the root path of each subtree added during the
merge and set explicit mergeinfo on it if it meets the aforementioned
conditions. */
- if (notify_b->added_paths)
- {
- apr_hash_index_t *hi;
-
- for (hi = apr_hash_first(NULL, notify_b->added_paths); hi;
- hi = apr_hash_next(hi))
- {
- const void *key;
- const char *added_path;
- const svn_string_t *added_path_parent_propval;
-
- apr_hash_this(hi, &key, NULL, NULL);
- added_path = key;
-
- apr_pool_clear(iterpool);
-
- /* Rather than using svn_client__get_wc_mergeinfo() and
- analyzing the mergeinfo it returns to determine if
- ADDED_PATH's parent has non-inheritable mergeinfo, it is
- much simpler to just get the svn_string_t representation
- of the svn:mergeinfo prop and look for the '*'
- non-inheritable marker. */
- SVN_ERR(svn_wc_prop_get(&added_path_parent_propval,
- SVN_PROP_MERGEINFO,
- svn_path_dirname(added_path, iterpool),
- adm_access, iterpool));
- if (added_path_parent_propval
- && strstr(added_path_parent_propval->data,
- SVN_MERGEINFO_NONINHERITABLE_STR))
- {
- /* ADDED_PATH's immediate parent has non-inheritable
- mergeinfo. */
- svn_boolean_t inherited;
- svn_merge_range_t *rng;
- svn_mergeinfo_t merge_mergeinfo, added_path_mergeinfo;
- apr_array_header_t *rangelist;
- const svn_wc_entry_t *entry;
- const char *rel_added_path,
- *abs_added_path, *abs_target_path,
- *added_path_mergeinfo_path;
-
- SVN_ERR(svn_wc__entry_versioned(&entry, added_path,
- adm_access, FALSE,
- iterpool));
-
- /* Calculate the mergeinfo resulting from this merge. */
- merge_mergeinfo = apr_hash_make(iterpool);
- rangelist = apr_array_make(iterpool, 1,
- sizeof(svn_merge_range_t *));
- rng = svn_merge_range_dup(&range, iterpool);
- if (entry->kind == svn_node_file)
- rng->inheritable = TRUE;
- else
- rng->inheritable =
- (!(depth == svn_depth_infinity
- || depth == svn_depth_immediates));
- APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = rng;
-
- /* Create the new mergeinfo path for
- added_path's mergeinfo. */
- SVN_ERR(svn_path_get_absolute(&abs_target_path,
- target_merge_path->path,
- iterpool));
- SVN_ERR(svn_path_get_absolute(&abs_added_path,
- added_path,
- iterpool));
-
- /* abs_added_path had better be a child of abs_target_path
- or something is *really* wrong. */
-
- rel_added_path = svn_path_is_child(abs_target_path,
- abs_added_path,
- iterpool);
- SVN_ERR_ASSERT(rel_added_path);
- added_path_mergeinfo_path = svn_path_join(mergeinfo_path,
- rel_added_path,
- iterpool);
- apr_hash_set(merge_mergeinfo,
- added_path_mergeinfo_path,
- APR_HASH_KEY_STRING, rangelist);
-
- /* Get any explicit mergeinfo the added path has. */
- SVN_ERR(svn_client__get_wc_mergeinfo(
- &added_path_mergeinfo, &inherited, FALSE,
- svn_mergeinfo_explicit, entry, added_path,
- NULL, NULL, adm_access, merge_b->ctx, iterpool));
-
- /* Combine the explict mergeinfo on the added path (if any)
- with the mergeinfo for this merge. */
- if (added_path_mergeinfo)
- SVN_ERR(svn_mergeinfo_merge(merge_mergeinfo,
- added_path_mergeinfo,
- iterpool));
- SVN_ERR(svn_client__record_wc_mergeinfo(added_path,
- merge_mergeinfo,
- adm_access,
- iterpool));
- }
- }
- }
- } /* (!merge_b->dry_run && merge_b->same_repos) */
+ SVN_ERR(record_mergeinfo_for_added_subtrees(&range,
+ mergeinfo_path,
+ depth,
+ notify_b,
+ merge_b,
+ adm_access,
+ pool));
+ }
- svn_pool_destroy(iterpool);
return err;
}
@@ -7066,6 +7410,7 @@ do_merge(apr_array_header_t *merge_sourc
/* Populate the portions of the merge context baton that need to
be reset for each merge source iteration. */
merge_cmd_baton.merge_source = *merge_source;
+ merge_cmd_baton.implicit_src_gap = NULL;
merge_cmd_baton.added_path = NULL;
merge_cmd_baton.add_necessitated_merge = FALSE;
merge_cmd_baton.dry_run_deletions =
@@ -7663,6 +8008,9 @@ ensure_all_missing_ranges_are_phantoms(s
path@TARGET_REV. Effectively this is the mergeinfo catalog on the
reintegrate target.
+ YC_ANCESTOR_REV is the revision of the youngest common ancestor of the
+ reintegrate source and the reintegrate target.
+
SOURCE_REPOS_REL_PATH is the path of the reintegrate source relative to
the root of the repository. TARGET_REPOS_REL_PATH is the path of the
reintegrate target relative to the root of the repository.
@@ -7696,6 +8044,7 @@ static svn_error_t *
find_unmerged_mergeinfo(svn_mergeinfo_catalog_t *unmerged_to_source_catalog,
svn_boolean_t *never_synched,
svn_revnum_t *youngest_merged_rev,
+ svn_revnum_t yc_ancestor_rev,
svn_mergeinfo_catalog_t source_catalog,
apr_hash_t *target_segments_hash,
const char *source_repos_rel_path,
@@ -7745,6 +8094,16 @@ find_unmerged_mergeinfo(svn_mergeinfo_ca
segments,
iterpool));
+ /* Remove any target history that is also part of the source's history,
+ i.e. their common ancestry. By definition this has already been
+ "merged" from the target to the source. If the source has explict
+ self referential mergeinfo it would intersect with the target's
+ history below, making it appear that some merges had been done from
+ the target to the source, when this might not actually be the case. */
+ SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
+ &target_history_as_mergeinfo, target_history_as_mergeinfo,
+ source_rev, yc_ancestor_rev, iterpool));
+
/* Look for any explicit mergeinfo on the source path corresponding to
the target path. If we find any remove that from SOURCE_CATALOG.
When this iteration over TARGET_SEGMENTS_HASH is complete all that
@@ -8034,6 +8393,9 @@ calculate_left_hand_side(const char **ur
apr_hash_t *segments_hash = apr_hash_make(pool);
svn_boolean_t never_synced;
svn_revnum_t youngest_merged_rev;
+ const char *yc_ancestor_path;
+ const char *source_url;
+ const char *target_url;
/* Get the history (segments) for the target and any of its subtrees
with explicit mergeinfo. */
@@ -8058,6 +8420,26 @@ calculate_left_hand_side(const char **ur
APR_HASH_KEY_STRING, segments);
}
+ /* Check that SOURCE_URL@SOURCE_REV and TARGET_URL@TARGET_REV are
+ actually related, we can't reintegrate if they are not. Also
+ get an initial value for *REV_LEFT. */
+ source_url = svn_path_url_add_component2(source_repos_root,
+ source_repos_rel_path,
+ subpool),
+ target_url = svn_path_url_add_component2(source_repos_root,
+ target_repos_rel_path,
+ subpool);
+ SVN_ERR(svn_client__get_youngest_common_ancestor(&yc_ancestor_path,
+ rev_left,
+ source_url, source_rev,
+ target_url, target_rev,
+ ctx, subpool));
+ if (!(yc_ancestor_path && SVN_IS_VALID_REVNUM(*rev_left)))
+ return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
+ _("'%s@%ld' must be ancestrally related to "
+ "'%s@%ld'"), source_url, source_rev,
+ target_url, target_rev);
+
/* Get the mergeinfo from the source, including its descendants
with differing explicit mergeinfo. */
APR_ARRAY_PUSH(source_repos_rel_path_as_array, const char *)
@@ -8075,6 +8457,7 @@ calculate_left_hand_side(const char **ur
SVN_ERR(find_unmerged_mergeinfo(&unmerged_catalog,
&never_synced,
&youngest_merged_rev,
+ *rev_left,
mergeinfo_catalog,
segments_hash,
source_repos_rel_path,
@@ -8094,24 +8477,6 @@ calculate_left_hand_side(const char **ur
if (never_synced)
{
/* We never merged to the source. Just return the branch point. */
- const char *yc_ancestor_path,
- *source_url = svn_path_url_add_component2(source_repos_root,
- source_repos_rel_path,
- subpool),
- *target_url = svn_path_url_add_component2(source_repos_root,
- target_repos_rel_path,
- subpool);
-
- SVN_ERR(svn_client__get_youngest_common_ancestor(&yc_ancestor_path,
- rev_left,
- source_url, source_rev,
- target_url, target_rev,
- ctx, subpool));
- if (!(yc_ancestor_path && SVN_IS_VALID_REVNUM(*rev_left)))
- return svn_error_createf(SVN_ERR_CLIENT_NOT_READY_TO_MERGE, NULL,
- _("'%s@%ld' must be ancestrally related to "
- "'%s@%ld'"), source_url, source_rev,
- target_url, target_rev);
*url_left = svn_path_url_add_component2(source_repos_root,
yc_ancestor_path, pool);
}