Index: subversion/libsvn_client/merge.c =================================================================== --- subversion/libsvn_client/merge.c (revision 31824) +++ subversion/libsvn_client/merge.c (working copy) @@ -1519,7 +1519,337 @@ (*notify_b->wrapped_func)(notify_b->wrapped_baton, notify, pool); } +/* Helper for the numerous times we need to allocate and initialize + a rangelist with one element. + + Return a rangelist allocated in POOL with one svn_merge_range_t + element defined by START, END, and INHERITABLE. */ +static apr_array_header_t * +init_rangelist(svn_revnum_t start, + svn_revnum_t end, + svn_boolean_t inheritable, + apr_pool_t *pool) +{ + apr_array_header_t *rangelist = + apr_array_make(pool, 1, sizeof(svn_merge_range_t *)); + svn_merge_range_t *range = apr_pcalloc(pool, sizeof(*range)); + range->start = start; + range->end = end; + range->inheritable = inheritable; + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = range; + return rangelist; +} + +/* Helper for the numerous times we need to allocate a svn_merge_range_t + and push it onto a rangelist. + + Allocated a svn_merge_range_t element defined by START, END, and + INHERITABLE and push it onto RANGELIST. */ +static void +push_range(apr_array_header_t *rangelist, + svn_revnum_t start, + svn_revnum_t end, + svn_boolean_t inheritable, + apr_pool_t *pool) +{ + svn_merge_range_t *range = apr_pcalloc(pool, sizeof(*range)); + + range->start = start; + range->end = end; + range->inheritable = inheritable; + APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = range; +} + +/* Helper for filter_merged_revisions() when operating on a subtree. + Like filter_merged_revisions(), this should only be called when + honoring mergeinfo. + + Filter the requested ranges being merged to a subtree so that we + don't try to describe invalid subtrees to the merge report editor. + Note in *CHILD_DELETED_OR_NONEXISTANT if the subtree doesn't exist, + is deleted, or is renamed in the requested range. + + PARENT, MERGEINFO_PATH, REVISION1, REVISION2, PRIMARY_URL, RA_SESSION, + and CTX are all cascaded from filter_merged_revisions(). + + Since this function is only invoked for subtrees of the merge target, + the guarantees afforded by normalize_merge_sources() don't apply. + Therefore it is possible that PRIMARY_URL@REVISION1 and + PRIMARY_URL@REVISION2 don't describe an unbroken line of history. + + Specifically we can end up with one of these eight cases: + + Note1: Unless noted otherwise, every case sets *REQUESTED_RANGELIST + to a rangelist with one element defined by REVISION1 and + REVISION2. + + Note2: The inheritability of all svn_merge_range_t in + *REQUESTED_RANGELIST is always TRUE. + + Forward Merges, i.e. REVISION1 < REVISION2 (PEG_REV) + + A) Requested range deletes subtree. + + PRIMARY_URL@REVISION1 exists, but PRIMARY_URL@REVISION2 doesn't + exist because PRIMARY_URL was deleted prior to REVISION2. + + Set *CHILD_DELETED_OR_NONEXISTANT to TRUE. + + B) Part of requested range predates subtree's existance. + + PRIMARY_URL@REVISION2 exists, but PRIMARY_URL@REVISION1 doesn't + exist because PRIMARY_URL didn't come into existence until some + revision 'N' where N > REVISION1. + + Set *CHILD_DELETED_OR_NONEXISTANT to FALSE. Populate + *REQUESTED_RANGELIST with the ranges between N and REVISION2 + (inclusive) at which PRIMARY_URL exists. Then take the intersection + of REVISION1:N (i.e. the range which predates the existance of + PRIMARY_URL) and PARENT->REQUESTED_RANGELIST and add it to + *REQUESTED_RANGELIST. This prevents us from later trying to describe + any non-existant path/revs for this subtree in + drive_merge_report_editor(). A good thing as that would break the + editor. + + C) Subtree doesn't exist in requested range or exists inside the + requested range but is ultimately deleted. + + Neither PRIMARY_URL@REVISION1 or PRIMARY_URL@REVISION2 exist. + + Set *CHILD_DELETED_OR_NONEXISTANT to TRUE + + D) Subtree exists at start and end of requested range and was + not replaced within that range. + + PRIMARY_URL@REVISION1 and PRIMARY_URL@REVISION2 both exist. + + Set *CHILD_DELETED_OR_NONEXISTANT to FALSE. + + Reverse Merges, i.e. REVISION1 (PEG_REV) > REVISION2 + + E) Part of requested range postdates subtree's existance. + + PRIMARY_URL@REVISION2 exists, but PRIMARY_URL@REVISION1 doesn't + exist because PRIMARY_URL was deleted prior to REVISION1. + + Set *CHILD_DELETED_OR_NONEXISTANT to FALSE. + + ### This is tricky, sort of the inverse of B; we want to reverse + ### merge some range M:N, let's say 14:4, into the subtree, but the + ### subtree was deleted at r10. *BUT* we only allow reverse merges + ### of ranges that exist in implicit or explicit mergeinfo. Can't we + ### simply set *REQUESTED_RANGELIST to REVISION1:REVISION2 and let the + ### existing code in filter_merged_revisions() do its thing? Because + ### if the subtree has any explicit mergeinfo (via inheritance) + ### describing ranges that postdate the subtree's existance, the + ### subtree's nearest parent must also have that mergeinfo right? + ### Put another way, how can all of the following ever be true? + ### + ### i) The subtree merge source doesn't exist anymore at + ### revsion X. + ### ii) Mergeinfo for X is explicitly set on the subtree. + ### iii) The subtree's parent has no explicit mergeinfo for X. + ### + ### This is where Kamesh utilized his recursive guess_live_ranges + ### function...But do we ever need to do this in practice? + + F) Requested range deletes (or replaces) a subtree. + + PRIMARY_URL@REVISION1 exists, but PRIMARY_URL@REVISION2 doesn't + exist because PRIMARY_URL didn't come into existence until + *after* REVISION2. Or PRIMARY_URL@REVISION1 and PRIMARY_URL@REVISION2 + both exist, but they don't describe an unbroken line of history. + + Set *CHILD_DELETED_OR_NONEXISTANT to TRUE. + + G) Subtree doesn't exist in requested range or exists inside the + requested range but is ultimately deleted. + + Neither PRIMARY_URL@REVISION1 or PRIMARY_URL@REVISION2 exist. + + Set *CHILD_DELETED_OR_NONEXISTANT to TRUE. + + H) Subtree exists at start and end of requested range and was + not replaced within that range. + + PRIMARY_URL@REVISION1 and PRIMARY_URL@REVISION2 both exist and + describe the start and end of an unbroken line of history. + + Set *CHILD_DELETED_OR_NONEXISTANT to FALSE. + + All the allocations are made from POOL. */ +static svn_error_t * +prepare_subtree_ranges(apr_array_header_t **requested_rangelist, + svn_boolean_t *child_deleted_or_nonexistant, + const char *mergeinfo_path, + svn_client__merge_path_t *parent, + svn_revnum_t revision1, + svn_revnum_t revision2, + const char *primary_url, + svn_ra_session_t *ra_session, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + svn_boolean_t is_rollback = revision2 < revision1; + svn_revnum_t peg_rev = is_rollback ? revision1 : revision2; + svn_revnum_t start_rev = is_rollback ? revision1 : revision2; + svn_revnum_t end_rev = is_rollback ? revision2 : revision1; + apr_array_header_t *segments; + const char *rel_source_path; + const char *session_url; + svn_error_t *err; + + SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool)); + SVN_ERR(svn_client__path_relative_to_root(&rel_source_path, + primary_url, + session_url, + FALSE, + ra_session, + NULL, + pool)); + err = svn_client__repos_location_segments(&segments, ra_session, + rel_source_path, peg_rev, + start_rev, end_rev, ctx, pool); + + /* If REL_SOURCE_PATH@PEG_REV doesn't exist then + svn_client__repos_location_segments() typically returns an + SVN_ERR_FS_NOT_FOUND error, but if it doesn't exist for a + forward merge over ra_neon then we get SVN_ERR_RA_DAV_REQUEST_FAILED. + http://subversion.tigris.org/issues/show_bug.cgi?id=3137 fixed some of + the cases where different RA layers returned different error codes to + signal the "path not found"...but it looks like there is more to do. */ + if (err) + { + if (err->apr_err == SVN_ERR_FS_NOT_FOUND + || err->apr_err == SVN_ERR_RA_DAV_REQUEST_FAILED) + { + svn_error_clear(err); + if (is_rollback) + { + svn_dirent_t *dirent; + SVN_ERR(svn_ra_stat(ra_session, rel_source_path, + revision2, &dirent, pool)); + if (dirent) + *child_deleted_or_nonexistant = FALSE; /* Case E. */ + else + *child_deleted_or_nonexistant = TRUE; /* Case G. */ + } + else + { + *child_deleted_or_nonexistant = TRUE; /* Case A & C. */ + } + + *requested_rangelist = init_rangelist(revision1, revision2, + TRUE, pool); + } + else + return err; + } + else + { + if (segments->nelts) + { + svn_location_segment_t *segment = + APR_ARRAY_IDX(segments, 0, svn_location_segment_t *); + if (is_rollback) + { + if (segment->range_start == revision2 + && segment->range_end == revision1) + { + /* Case H. */ + *requested_rangelist = init_rangelist(revision1, revision2, + TRUE, pool); + *child_deleted_or_nonexistant = FALSE; + } + else /* Multiple location segements found. */ + { + /* Case F. */ + *requested_rangelist = init_rangelist(revision1, revision2, + TRUE, pool); + *child_deleted_or_nonexistant = TRUE; + } + } + else /* Forward merge */ + { + /* Again, because REVISION2 is the peg revision for the call + to svn_client__repos_location_segments, we know that the + range_end of the last segment in segments is equal to + REVISION2. */ + if (segment->range_start == revision1 + && segment->range_end == revision2) + { + /* Case D. */ + *requested_rangelist = init_rangelist(revision1, revision2, + TRUE, pool); + *child_deleted_or_nonexistant = FALSE; + } + else /* segment->range_start != revision1, since + segment->range_start can't be less than REVISION1, + this implies revision1 < segment->range_start. */ + { + /* Case B. */ + int i; + apr_array_header_t *predate_intersection_rangelist; + apr_array_header_t *different_name_rangelist = + apr_array_make(pool, 1, sizeof(svn_merge_range_t *)); + + /* Make a ranglist that describes the range which predates + PRIMARY_URL's existance... */ + apr_array_header_t *predate_rangelist = + init_rangelist(revision1, + segment->range_start, + TRUE, pool); + /* ...Find the intersection of that rangelist and the + subtree's parent's remaining ranges. */ + SVN_ERR(svn_rangelist_intersect( + &predate_intersection_rangelist, + predate_rangelist, + parent->remaining_ranges, + FALSE, pool)); + *requested_rangelist = + init_rangelist(segment->range_start, + revision2, + TRUE, pool); + /* Merge *REQUESTED_RANGELIST with its parent's remaining + ranges the intersect with the subtree's prehistory. */ + SVN_ERR(svn_rangelist_merge( + requested_rangelist, predate_intersection_rangelist, + pool)); + + /* Remove ranges that predate PRIMARY_URL's existance + because the source exists under a different URL due to a + rename between REVISION1:REVISION2 - see 'MERGE FAILS' in + http://subversion.tigris.org/issues/show_bug.cgi?id=3067#desc34. + */ + if (segment->path /* Is NULL if a gap in history. */ + && strcmp(segment->path, mergeinfo_path + 1) != 0) + push_range(different_name_rangelist, segment->range_start, + segment->range_end, TRUE, pool); + for (i = 1; i < segments->nelts; i++) + { + segment = + APR_ARRAY_IDX(segments, i, svn_location_segment_t *); + if (segment->path + && strcmp(segment->path, mergeinfo_path + 1) != 0) + push_range(different_name_rangelist, + segment->range_start, + segment->range_end, TRUE, pool); + } + if (different_name_rangelist->nelts) + SVN_ERR(svn_rangelist_remove(requested_rangelist, + different_name_rangelist, + *requested_rangelist, FALSE, + pool)); + *child_deleted_or_nonexistant = FALSE; + } + } + } + } /* ! err */ + + return SVN_NO_ERROR; +} + /*-----------------------------------------------------------------------*/ /*** Determining What Remains To Be Merged ***/ @@ -1528,9 +1858,14 @@ Calculate the ranges that remain to be merged from the merge source MERGEINFO_PATH (relative to the repository root) to the working - copy path represented by ENTRY -- for use by drive_merge_report_editor()'s - application of the editor to the WC. + copy path represented by CHILD -- for use by drive_merge_report_editor()'s + application of the editor to the WC. Set CHILD->remaining_ranges to the + set of revisions to merge. + PARENT, PRIMARY_URL, IS_SUBTREE, RA_SESSION, CTX are cascaded from + calculate_remaining_ranges(). If IS_SUBTREE is FALSE then PARENT is + ignored. + REVISION1 and REVISION2 describe the merge range requested from MERGEINFO_PATH. @@ -1544,38 +1879,87 @@ NOTE: This should only be called when honoring mergeinfo. */ static svn_error_t * -filter_merged_revisions(apr_array_header_t **remaining_ranges, +filter_merged_revisions(svn_client__merge_path_t *parent, + svn_client__merge_path_t *child, const char *mergeinfo_path, svn_mergeinfo_t target_mergeinfo, svn_mergeinfo_t implicit_mergeinfo, svn_revnum_t revision1, svn_revnum_t revision2, - const svn_wc_entry_t *entry, + const char *primary_url, + svn_ra_session_t *ra_session, + svn_boolean_t is_subtree, + svn_client_ctx_t *ctx, apr_pool_t *pool) { apr_array_header_t *target_rangelist = NULL; svn_mergeinfo_t mergeinfo = implicit_mergeinfo; apr_array_header_t *requested_merge; - svn_merge_range_t *range; - /* Convert REVISION1 and REVISION2 to a rangelist. */ - range = apr_pcalloc(pool, sizeof(*range)); - requested_merge = apr_array_make(pool, 1, sizeof(range)); - range->start = revision1; - range->end = revision2; - /* Talking about a requested merge range's inheritability doesn't - make much sense, but as we are using svn_merge_range_t to describe - it we need to pick *something*. Since all the rangelist - manipulations in this function either don't consider inheritance - by default or we are requesting that they don't (i.e. - svn_rangelist_remove and svn_rangelist_intersect) then we could - set the inheritability as FALSE, it won't matter either way. */ - range->inheritable = TRUE; + if (is_subtree) + { + /* If CHILD is the merge target we then know that PRIMARY_URL, + REVISION1, and REVISION2 are provided by normalize_merge_sources() + -- see 'MERGEINFO MERGE SOURCE NORMALIZATION'. Due to this + normalization we know that PRIMARY_URL@REVISION1 and + PRIMARY_URL@REVISION2 describe an unbroken line of history such + that the entire range described by REVISION1:REVISION2 can + potentially be merged to CHILD. So we simply convert REVISION1 and + REVISION2 to a rangelist and proceed to the filtering of merged + revisions. - APR_ARRAY_PUSH(requested_merge, svn_merge_range_t *) = range; + But if CHILD is a subtree we don't have the same guarantees about + PRIMARY_URL, REVISION1, and REVISION2 as we do for the merge target. + PRIMARY_URL@REVSION1 and/or PRIMARY_URL@REVSION2 might not exist. - if (revision1 > revision2) /* Is this a reverse merge? */ + If one or both doesn't exist, we need to know so we don't later try + to describe these invalid subtrees in drive_merge_report_editor(), + as that will break the merge. */ + svn_boolean_t child_deleted_or_nonexistant; + SVN_ERR(prepare_subtree_ranges(&requested_merge, + &child_deleted_or_nonexistant, + mergeinfo_path, parent, + revision1, revision2, + primary_url, ra_session, ctx, pool)); + if (child_deleted_or_nonexistant && parent) + { + /* A little trick: If CHILD is a subtree which will be deleted by + the requested merge or simply doesn't exist along the line of + history described by PRIMARY_URL@REVSION1 -> PRIMARY_URL@REVSION2, + then don't bother dealing with CHILD in a separate editor drive. + Just make child's remaining ranges exactly the same as its + nearest parent. + + For deletions this will cause the editor drive to be rooted at + the subtree CHILD's nearest parent in CHILDREN_WITH_MERGEINFO + This will simply delete the subtree. For the case where neither + PRIMARY_URL@REVSION1 or PRIMARY_URL@REVSION2 exist, there is + nothing to merge to the subtree, so ignoring it completely is + safe. See + http://subversion.tigris.org/issues/show_bug.cgi?id=3067#desc5. + */ + child->remaining_ranges = + svn_rangelist_dup(parent->remaining_ranges, pool); + return SVN_NO_ERROR; + } + } + else { + /* Convert REVISION1 and REVISION2 to a rangelist. + + Note: Talking about a requested merge range's inheritability doesn't + make much sense, but as we are using svn_merge_range_t to describe + it we need to pick *something*. Since all the rangelist + manipulations in this function either don't consider inheritance + by default or we are requesting that they don't (i.e. + svn_rangelist_remove and svn_rangelist_intersect) then we could + set the inheritability as FALSE, it won't matter either way. */ + requested_merge = init_rangelist(revision1, revision2, TRUE, pool); + } + + /* Now filter out revisions that have already been merged to CHILD. */ + if (revision1 > revision2) /* This is a reverse merge. */ + { if (target_mergeinfo) { mergeinfo = svn_mergeinfo_dup(implicit_mergeinfo, pool); @@ -1608,21 +1992,21 @@ even if that path has no explicit mergeinfo prior to the merge -- See condition 3 in the doc string for merge.c:get_mergeinfo_paths(). */ - SVN_ERR(svn_rangelist_intersect(remaining_ranges, + SVN_ERR(svn_rangelist_intersect(&(child->remaining_ranges), target_rangelist, requested_merge, FALSE, pool)); - SVN_ERR(svn_rangelist_reverse(*remaining_ranges, pool)); + SVN_ERR(svn_rangelist_reverse(child->remaining_ranges, pool)); } else { - *remaining_ranges = + child->remaining_ranges = apr_array_make(pool, 1, sizeof(svn_merge_range_t *)); } } - else + else /* This is a forward merge */ { - *remaining_ranges = requested_merge; + child->remaining_ranges = requested_merge; /* ### TODO: Which evil shall we choose? ### @@ -1660,29 +2044,44 @@ /* See earlier comment preceeding svn_rangelist_intersect() for why we don't consider inheritance here. */ if (target_rangelist) - SVN_ERR(svn_rangelist_remove(remaining_ranges, target_rangelist, + SVN_ERR(svn_rangelist_remove(&(child->remaining_ranges), + target_rangelist, requested_merge, FALSE, pool)); } return SVN_NO_ERROR; } -/* Populate *REMAINING_RANGES with a list of revision ranges - constructed by taking after removing already-merged ranges. - Cascade SOURCE_ROOT_URL, URL1, REVISION1, URL2, REVISION2, INHERITABLE, - TARGET_MERGEINFO, IMPLICIT_MERGEINFO, RA_SESSION, ENTRY, CTX, and POOL. +/* Helper for do_file_merge and do_directory_merge (by way of + populate_remaining_ranges() for the latter). - See `MERGEINFO MERGE SOURCE NORMALIZATION' for more requirements - around the values of URL1, REVISION1, URL2, and REVISION2. + Determine what portions of URL1@REVISION1 -> URL2@REVISION2 have already + been merged to CHILD->PATH and populate CHILD->REMAINING_RANGES with the + ranges that still need merging. + SOURCE_ROOT_URL, URL1, REVISION1, URL2, REVISION2, TARGET_MERGEINFO, + IMPLICIT_MERGEINFO, RA_SESSION, and CTX are all cascaded from the + caller's arguments of the same names. + + If IS_SUBTREE is FALSE then CHILD describes the merge target and the + requirements around the values of URL1, REVISION1, URL2, and REVISION2 + described in 'MERGEINFO MERGE SOURCE NORMALIZATION' hold. If IS_SUBTREE + is TRUE then CHILD describes some subtree of a merge target and these + normalization conditions do not necessarily hold. IS_SUBTREE should + always be FALSE when calling from do_file_merge(). + + If IS_SUBTREE is FALSE then PARENT is ignored, otherwise PARENT must + represent the nearest working copy ancestor of CHILD. + NOTE: This should only be called when honoring mergeinfo. - When performing reverse merges, return SVN_ERR_CLIENT_NOT_READY_TO_MERGE if - URL1@REVISION1, URL2@REVISION2, and ENTRY are all on the same line of - history but ENTRY-REVISION is older than the REVISION1-REVISION2 range, see - comment re issue #2973 below. + NOTE: When performing reverse merges, return + SVN_ERR_CLIENT_NOT_READY_TO_MERGE if URL1@REVISION1, URL2@REVISION2, and + ENTRY are all on the same line of history but ENTRY-REVISION is older than + the REVISION1-REVISION2 range, see comment re issue #2973 below. */ static svn_error_t * -calculate_remaining_ranges(apr_array_header_t **remaining_ranges, +calculate_remaining_ranges(svn_client__merge_path_t *parent, + svn_client__merge_path_t *child, const char *source_root_url, const char *url1, svn_revnum_t revision1, @@ -1690,6 +2089,7 @@ svn_revnum_t revision2, svn_mergeinfo_t target_mergeinfo, svn_mergeinfo_t implicit_mergeinfo, + svn_boolean_t is_subtree, svn_ra_session_t *ra_session, const svn_wc_entry_t *entry, svn_client_ctx_t *ctx, @@ -1698,14 +2098,14 @@ const char *mergeinfo_path; const char *primary_url = (revision1 < revision2) ? url2 : url1; - /* Determine which portions of the requested merge range still - need merging. */ + /* 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)); - SVN_ERR(filter_merged_revisions(remaining_ranges, mergeinfo_path, + SVN_ERR(filter_merged_revisions(parent, child, mergeinfo_path, target_mergeinfo, implicit_mergeinfo, - revision1, revision2, entry, pool)); + revision1, revision2, primary_url, + ra_session, is_subtree, ctx, pool)); /* Issue #2973 -- from the continuing series of "Why, since the advent of merge tracking, allowing merges into mixed rev and locally modified @@ -1733,7 +2133,7 @@ So in the name of user friendliness, return an error suggesting a helpful course of action. */ - if (((*remaining_ranges)->nelts == 0) + if (((child->remaining_ranges)->nelts == 0) && (revision2 < revision1) && (entry->revision <= revision2)) { @@ -1943,6 +2343,7 @@ const char *child_url1, *child_url2; svn_client__merge_path_t *child = APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *); + svn_client__merge_path_t *parent = NULL; /* If the path is absent don't do subtree merge either. */ if (!child || child->absent) @@ -1969,12 +2370,29 @@ MIN(revision1, revision2), adm_access, merge_b->ctx, pool)); - SVN_ERR(calculate_remaining_ranges(&(child->remaining_ranges), + /* If CHILD isn't the merge target find its parent. */ + if (i > 0) + { + int parent_index = find_nearest_ancestor(children_with_mergeinfo, + FALSE, child->path); + parent = APR_ARRAY_IDX(children_with_mergeinfo, parent_index, + svn_client__merge_path_t *); + if (!parent) + { + /* If CHILD is a subtree then its parent must be in + CHILDREN_WITH_MERGEINFO, see the global comment + 'THE CHILDREN_WITH_MERGEINFO ARRAY'. */ + abort(); + } + } + + SVN_ERR(calculate_remaining_ranges(parent, child, source_root_url, child_url1, revision1, child_url2, revision2, child->pre_merge_mergeinfo, child->implicit_mergeinfo, + i > 0 ? TRUE : FALSE, /* is subtree */ ra_session, child_entry, merge_b->ctx, pool)); } @@ -2894,11 +3312,12 @@ svn_path_add_component(merge_src_child_path, path); SVN_ERR(svn_mergeinfo_parse(&mergehash, propval->data, pool)); - if (apr_hash_get(mergehash, merge_src_child_path->data, - APR_HASH_KEY_STRING)) + if (propval->len == 0 /* empty mergeinfo */ + || apr_hash_get(mergehash, merge_src_child_path->data, + APR_HASH_KEY_STRING)) { /* The easy way: PATH already has mergeinfo - from this source... */ + from this source or has empty mergeinfo... */ has_mergeinfo_from_merge_src = TRUE; } else @@ -2955,6 +3374,10 @@ svn_error_clear(err); else return err; + } + else /* PATH does exist in the merge source*/ + { + has_mergeinfo_from_merge_src = TRUE; } /* Reparent the session to its original URL if necessary. */ @@ -2963,67 +3386,6 @@ SVN_ERR(svn_ra_reparent(wb->ra_session, original_ra_url, pool)); } - - if (!err) /* PATH does exist in the merge source*/ - { - if (propval->len > 0) - { - has_mergeinfo_from_merge_src = TRUE; - } - else /* Handle the special case of empty mergeinfo */ - { - /* WC->WC copies (and moves) can't contact the server - and so leave empty mergefino on the copy target to - prevent it from erroneously inheriting the incorrect - mergeinfo from the repos (if any exists) during a - merge. Reverse merges may also leave empty mergeinfo - on a path so this state is not exclusive to WC->WC - copies. - - Because the WC->WC copy behavior is a safety net, - the empty mergeinfo on a path *may* not actually - override anything. In the case where this is true, - we don't need to treat PATH as a subtree with - intersecting mergeinfo, we can simply merge into - PATH's parent (which may be the merge target - itself)... - - ...So, if we find empty mergeinfo on PATH see if - PATH has any ancestor with mergeinfo. */ - svn_mergeinfo_t overidden_mergeinfo; - svn_boolean_t indirect; - SVN_ERR(svn_client__get_wc_or_repos_mergeinfo( - &overidden_mergeinfo, entry, - &indirect, FALSE, svn_mergeinfo_nearest_ancestor, - wb->ra_session, path, wb->base_access, wb->ctx, - pool)); - if (indirect) - { - /* Ok, we found empty mergeinfo which overrides some - parent with mergeinfo. Time for one final - optimization: If the mergeinfo overridden is - *also* empty, then we can perform some preemptive - elision right now and remove the empty mergeinfo - from PATH. Even if the overriden parent is in - the repository it's ok, since the merge target - *always* gets mergeinfo set (though it may - elide).*/ - svn_boolean_t equal_mergeinfo; - SVN_ERR(svn_mergeinfo__equals(&equal_mergeinfo, - mergehash, - overidden_mergeinfo, - FALSE, pool)); - if (equal_mergeinfo) - SVN_ERR(svn_client__record_wc_mergeinfo( - path, NULL, wb->base_access, pool)); - else - /* The empty mergeinfo on PATH really means - something. Merge this subtree separately. */ - has_mergeinfo_from_merge_src = TRUE; - } - } - } /* (!err) */ - start_url = NULL; } /* the slightly harder way */ } /* Regardless of whether PATH has explicit mergeinfo or not, we must @@ -3334,7 +3696,8 @@ 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. + 1) Path has working svn:mergeinfo from corresponding merge source or + has empty mergeinfo. 2) Path is switched. 3) Path has no mergeinfo of its own but its parent has mergeinfo with non-inheritable ranges (in this case the function will actually set @@ -4127,6 +4490,8 @@ if (honor_mergeinfo) { const char *source_root_url; + svn_client__merge_path_t *merge_target = + apr_pcalloc(pool, sizeof(*merge_target)); SVN_ERR(svn_ra_get_repos_root2(merge_b->ra_session1, &source_root_url, pool)); @@ -4151,13 +4516,14 @@ by REVISION1:REVISION2. */ if (!merge_b->record_only) { - SVN_ERR(calculate_remaining_ranges(&remaining_ranges, + SVN_ERR(calculate_remaining_ranges(NULL, merge_target, source_root_url, url1, revision1, url2, revision2, target_mergeinfo, - implicit_mergeinfo, + implicit_mergeinfo, FALSE, merge_b->ra_session1, entry, ctx, pool)); + remaining_ranges = merge_target->remaining_ranges; } } Index: subversion/tests/cmdline/merge_tests.py =================================================================== --- subversion/tests/cmdline/merge_tests.py (revision 31824) +++ subversion/tests/cmdline/merge_tests.py (working copy) @@ -11171,8 +11171,6 @@ # that don't exist at the start of a merge range shouldn't break the # merge'. Specifically see # http://subversion.tigris.org/issues/show_bug.cgi?id=3067#desc5 -# -# Set as XFail until that issue is resolved. def dont_merge_revs_into_subtree_that_predate_it(sbox): "dont merge revs into a subtree that predate it" @@ -11308,7 +11306,7 @@ svntest.actions.run_and_verify_svn( None, expected_merge_output( - [[7,9]], ['U ' + os.path.join(short_H_COPY_path, "psi") + '\n', + [[6,9]], ['U ' + os.path.join(short_H_COPY_path, "psi") + '\n', 'D ' + os.path.join(short_H_COPY_path, "nu") + '\n']), [], 'merge', sbox.repo_url + '/A/D/H', short_H_COPY_path, '--force') os.chdir(saved_cwd) @@ -12458,8 +12456,6 @@ # or end of a merge range shouldn't break the merge' # # See http://subversion.tigris.org/issues/show_bug.cgi?id=3067#desc34 -# -# Set as XFail until that issue is resolved. def subtrees_with_empty_mergeinfo(sbox): "mergeinfo not set on subtree with empty mergeinfo" @@ -12666,8 +12662,8 @@ server_has_mergeinfo), SkipUnless(reverse_merge_away_all_mergeinfo, server_has_mergeinfo), - XFail(SkipUnless(dont_merge_revs_into_subtree_that_predate_it, - server_has_mergeinfo)), + SkipUnless(dont_merge_revs_into_subtree_that_predate_it, + server_has_mergeinfo), SkipUnless(merge_chokes_on_renamed_subtrees, server_has_mergeinfo), SkipUnless(dont_explicitly_record_implicit_mergeinfo, @@ -12677,8 +12673,8 @@ server_has_mergeinfo), SkipUnless(subtree_source_missing_in_requested_range, server_has_mergeinfo), - XFail(SkipUnless(subtrees_with_empty_mergeinfo, - server_has_mergeinfo)), + SkipUnless(subtrees_with_empty_mergeinfo, + server_has_mergeinfo), ] if __name__ == '__main__':