Merge improvements from upstream https://svn.collab.net/viewvc/svn?view=rev&revision=30748 https://svn.collab.net/viewvc/svn?view=rev&revision=31075 https://svn.collab.net/viewvc/svn?view=rev&revision=31151 https://svn.collab.net/viewvc/svn?view=rev&revision=31301 https://svn.collab.net/viewvc/svn?view=rev&revision=31312 https://svn.collab.net/viewvc/svn?view=rev&revision=31482 https://svn.collab.net/viewvc/svn?view=rev&revision=31504 https://svn.collab.net/viewvc/svn?view=rev&revision=31795 https://svn.collab.net/viewvc/svn?view=rev&revision=31798 https://svn.collab.net/viewvc/svn?view=rev&revision=31801 https://svn.collab.net/viewvc/svn?view=rev&revision=31817 --- subversion/libsvn_client/merge.c +++ subversion/libsvn_client/merge.c @@ -1311,12 +1311,17 @@ } notification_receiver_baton_t; -/* Finds a nearest ancestor in CHILDREN_WITH_MERGEINFO for PATH. +/* Finds a nearest ancestor in CHILDREN_WITH_MERGEINFO for PATH. If + PATH_IS_OWN_ANCESTOR is TRUE then a child in CHILDREN_WITH_MERGEINFO + where child->path == PATH is considered PATH's ancestor. If FALSE, + then child->path must be a proper ancestor of PATH. + CHILDREN_WITH_MERGEINFO is expected to be sorted in Depth first order of path. Nearest ancestor's index from CHILDREN_WITH_MERGEINFO is returned. */ static int find_nearest_ancestor(apr_array_header_t *children_with_mergeinfo, + svn_boolean_t path_is_own_ancestor, const char *path) { int i; @@ -1332,8 +1337,10 @@ { svn_client__merge_path_t *child = APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *); - if (svn_path_is_ancestor(child->path, path)) - ancestor_index = i; + if (svn_path_is_ancestor(child->path, path) + && (svn_path_compare_paths(child->path, path) != 0 + || path_is_own_ancestor)) + ancestor_index = i; } return ancestor_index; } @@ -1371,9 +1378,27 @@ /* See if this is an operative directory merge. */ if (!(notify_b->is_single_file_merge) && is_operative_notification) { + /* Find NOTIFY->PATH's nearest ancestor in + NOTIFY->CHILDREN_WITH_MERGEINFO. Normally we consider a child in + NOTIFY->CHILDREN_WITH_MERGEINFO representing PATH to be an + ancestor of PATH, but if this is a deletion of PATH then the + notification must be for a proper ancestor of PATH. This ensures + we don't get notifications like: + + --- Merging rX into 'PARENT/CHILD' + D PARENT/CHILD + + But rather: + + --- Merging rX into 'PARENT' + D PARENT/CHILD + */ int new_nearest_ancestor_index = - find_nearest_ancestor(notify_b->children_with_mergeinfo, - notify->path); + find_nearest_ancestor( + notify_b->children_with_mergeinfo, + notify->action == svn_wc_notify_update_delete ? FALSE : TRUE, + notify->path); + if (new_nearest_ancestor_index != notify_b->cur_ancestor_index) { svn_client__merge_path_t *child = @@ -1853,7 +1878,6 @@ const char *child_repos_path; const svn_wc_entry_t *child_entry; const char *child_url1, *child_url2; - svn_mergeinfo_t implicit_mergeinfo; svn_client__merge_path_t *child = APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *); @@ -1875,7 +1899,7 @@ FALSE, iterpool)); SVN_ERR(get_full_mergeinfo(&(child->pre_merge_mergeinfo), - &implicit_mergeinfo, child_entry, + &(child->implicit_mergeinfo), child_entry, &(child->indirect_mergeinfo), svn_mergeinfo_inherited, NULL, child->path, MAX(revision1, revision2), @@ -1888,7 +1912,7 @@ child_url2, revision2, inheritable, child->pre_merge_mergeinfo, - implicit_mergeinfo, + child->implicit_mergeinfo, ra_session, child_entry, merge_b->ctx, pool)); } @@ -1925,8 +1949,8 @@ /*** Other Helper Functions ***/ -/* Create mergeinfo describing the merge of RANGE into our target, accounting - for paths unaffected by the merge due to skips or conflicts from +/* 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. @@ -1934,19 +1958,17 @@ 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, - svn_merge_range_t *range, + 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_array_header_t *rangelist = apr_array_make(pool, 1, sizeof(range)); apr_size_t nbr_skips = (notify_b->skipped_paths != NULL ? apr_hash_count(notify_b->skipped_paths) : 0); *merges = apr_hash_make(pool); - APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = range; apr_hash_set(*merges, target_wcpath, APR_HASH_KEY_STRING, rangelist); if (nbr_skips > 0) { @@ -1978,7 +2000,7 @@ ### see issue #2915. */ apr_hash_set(*merges, (const char *) skipped_path, APR_HASH_KEY_STRING, - apr_array_make(pool, 0, sizeof(range))); + 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 @@ -1996,10 +2018,8 @@ hi = apr_hash_next(hi)) { const svn_wc_entry_t *child_entry; - svn_merge_range_t *child_merge_range; apr_array_header_t *rangelist_of_child = NULL; apr_hash_this(hi, &merged_path, NULL, NULL); - child_merge_range = svn_merge_range_dup(range, pool); SVN_ERR(svn_wc__entry_versioned(&child_entry, merged_path, adm_access, FALSE, @@ -2014,14 +2034,17 @@ 1. Merge target directory if depth is immediates. 2. If merge is on a file and requested depth is 'files'. */ - child_merge_range->inheritable = TRUE; - rangelist_of_child = apr_array_make(pool, 1, sizeof(range)); + 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_ARRAY_PUSH(rangelist_of_child, svn_merge_range_t *) = - child_merge_range; - apr_hash_set(*merges, (const char *)merged_path, APR_HASH_KEY_STRING, rangelist_of_child); } @@ -2510,16 +2533,24 @@ } } -/* For each child of CHILDREN_WITH_MERGEINFO create a new remaining_ranges - by removing the first item from the original range list and overwrite the - original remaining_ranges with this new list. - All the allocations are persistent from a POOL. - TODO, we should have remaining_ranges in reverse order to avoid recreating - the remaining_ranges every time instead of one 'pop' operation. */ +/* Helper for do_directory_merge(). + + Remove the first remaining revision range for each child in + CHILDREN_WITH_MERGEINFO *iff* that child was already merged. END_REV is the + ending revision of the most recently merged range, i.e. the same end_rev + passed to drive_merge_report_editor() by do_directory_merge(). If a + range is removed from a child's remaining_ranges array, allocate the new + remaining_ranges array in POOL. + + ### TODO: We should have remaining_ranges in reverse order to avoid + ### recreating and reallocationg the remaining_ranges every time we want + ### to remove the first range. If the ranges were reversed we could simply + ### pop the last element in the array. */ static void -remove_first_range_from_remaining_ranges( - apr_array_header_t *children_with_mergeinfo, - apr_pool_t *pool) +remove_first_range_from_remaining_ranges(svn_revnum_t end_rev, + apr_array_header_t + *children_with_mergeinfo, + apr_pool_t *pool) { int i, j; for (i = 0; i < children_with_mergeinfo->nelts; i++) @@ -2531,17 +2562,24 @@ continue; if (child->remaining_ranges->nelts > 0) { - apr_array_header_t *orig_remaining_ranges = child->remaining_ranges; - child->remaining_ranges = - apr_array_make(pool, (child->remaining_ranges->nelts - 1), - sizeof(svn_merge_range_t *)); - for (j = 1; j < orig_remaining_ranges->nelts; j++) + svn_merge_range_t *first_range = + APR_ARRAY_IDX(child->remaining_ranges, 0, svn_merge_range_t *); + if (first_range->end == end_rev) { - svn_merge_range_t *range = APR_ARRAY_IDX(orig_remaining_ranges, - j, - svn_merge_range_t *); - APR_ARRAY_PUSH(child->remaining_ranges, svn_merge_range_t *) - = range; + apr_array_header_t *orig_remaining_ranges = + child->remaining_ranges; + child->remaining_ranges = + apr_array_make(pool, (child->remaining_ranges->nelts - 1), + sizeof(svn_merge_range_t *)); + for (j = 1; j < orig_remaining_ranges->nelts; j++) + { + svn_merge_range_t *range = + APR_ARRAY_IDX(orig_remaining_ranges, + j, + svn_merge_range_t *); + APR_ARRAY_PUSH(child->remaining_ranges, + svn_merge_range_t *) = range; + } } } } @@ -3455,6 +3493,89 @@ } +/* Implements the svn_log_entry_receiver_t interface. */ +static svn_error_t * +log_changed_revs(void *baton, + svn_log_entry_t *log_entry, + apr_pool_t *pool) +{ + apr_array_header_t *revs = baton; + svn_revnum_t *revision = apr_palloc(revs->pool, sizeof(*revision)); + *revision = log_entry->revision; + APR_ARRAY_PUSH(revs, svn_revnum_t *) = revision; + return SVN_NO_ERROR; +} + + +/* Set *OPERATIVE_RANGES_P to an array of svn_merge_range_t * merge + range objects copied wholesale from RANGES which have the property + that in some revision within that range the object identified by + RA_SESSION was modified (if by "modified" we mean "'svn log' would + return that revision). *OPERATIVE_RANGES_P is allocated from the + same pool as RANGES, and the ranges within it are shared with + RANGES, too. Use POOL for temporary allocations. */ +static svn_error_t * +remove_noop_merge_ranges(apr_array_header_t **operative_ranges_p, + svn_ra_session_t *ra_session, + apr_array_header_t *ranges, + apr_pool_t *pool) +{ + int i; + svn_revnum_t oldest_rev = SVN_INVALID_REVNUM; + svn_revnum_t youngest_rev = SVN_INVALID_REVNUM; + apr_array_header_t *changed_revs = + apr_array_make(pool, ranges->nelts, sizeof(svn_revnum_t *)); + apr_array_header_t *operative_ranges = + apr_array_make(ranges->pool, ranges->nelts, ranges->elt_size); + apr_array_header_t *log_targets = + apr_array_make(pool, 1, sizeof(const char *)); + APR_ARRAY_PUSH(log_targets, const char *) = ""; + + /* Find the revision extremes of the RANGES we have. */ + for (i = 0; i < ranges->nelts; i++) + { + svn_merge_range_t *r = APR_ARRAY_IDX(ranges, i, svn_merge_range_t *); + svn_revnum_t max_rev = MAX(r->start, r->end); + svn_revnum_t min_rev = MIN(r->start, r->end) + 1; + + if ((! SVN_IS_VALID_REVNUM(youngest_rev)) || (max_rev > youngest_rev)) + youngest_rev = max_rev; + if ((! SVN_IS_VALID_REVNUM(oldest_rev)) || (min_rev < oldest_rev)) + oldest_rev = min_rev; + } + + /* Get logs across those ranges, recording which revisions hold + changes to our object's history. */ + SVN_ERR(svn_ra_get_log2(ra_session, log_targets, youngest_rev, + oldest_rev, 0, FALSE, FALSE, FALSE, + apr_array_make(pool, 0, sizeof(const char *)), + log_changed_revs, changed_revs, pool)); + + /* Now, copy from RANGES to *OPERATIVE_RANGES, filtering out ranges + that aren't operative (by virtue of not having any revisions + represented in the CHANGED_REVS array). */ + for (i = 0; i < ranges->nelts; i++) + { + svn_merge_range_t *range = APR_ARRAY_IDX(ranges, i, svn_merge_range_t *); + int j; + + for (j = 0; j < changed_revs->nelts; j++) + { + svn_revnum_t *changed_rev = + APR_ARRAY_IDX(changed_revs, j, svn_revnum_t *); + if ((*changed_rev > MIN(range->start, range->end)) + && (*changed_rev <= MAX(range->start, range->end))) + { + APR_ARRAY_PUSH(operative_ranges, svn_merge_range_t *) = range; + break; + } + } + } + *operative_ranges_p = operative_ranges; + return SVN_NO_ERROR; +} + + /*-----------------------------------------------------------------------*/ /*** Merge Source Normalization ***/ @@ -3859,6 +3980,54 @@ /*** Merge Workhorse Functions ***/ +/* Helper for do_directory_merge() and do_file_merge() which filters out a + path's own natural history from the mergeinfo describing a merge. + + Given the natural history IMPLICIT_MERGEINFO of some wc merge target path, + the repository relative merge source path SOURCE_REL_PATH, and the + requested merge range REQUESTED_RANGE from SOURCE_REL_PATH, remove any + portion of REQUESTED_RANGE which is already described in + IMPLICIT_MERGEINFO. Store the result in *FILTERED_RANGELIST. + + *FILTERED_RANGELIST is allocated in POOL. */ +static svn_error_t * +filter_natural_history_from_mergeinfo(apr_array_header_t **filtered_rangelist, + const char *source_rel_path, + svn_mergeinfo_t implicit_mergeinfo, + svn_merge_range_t *requested_range, + apr_pool_t *pool) +{ + /* Make the REQUESTED_RANGE into a rangelist. */ + apr_array_header_t *requested_rangelist = + apr_array_make(pool, 0, sizeof(svn_merge_range_t *)); + APR_ARRAY_PUSH(requested_rangelist, svn_merge_range_t *) = + svn_merge_range_dup(requested_range, pool); + + *filtered_rangelist = NULL; + + /* If the IMPLICIT_MERGEINFO already describes ranges associated + with SOURCE_REL_PATH then filter those ranges out. */ + if (implicit_mergeinfo) + { + apr_array_header_t *implied_rangelist = + apr_hash_get(implicit_mergeinfo, source_rel_path, + APR_HASH_KEY_STRING); + + if (implied_rangelist) + SVN_ERR(svn_rangelist_remove(filtered_rangelist, + implied_rangelist, + requested_rangelist, + FALSE, pool)); + } + + /* If no filtering was performed the filtered rangelist is + simply the requested rangelist.*/ + if (! (*filtered_rangelist)) + *filtered_rangelist = requested_rangelist; + + return SVN_NO_ERROR; +} + /* The single-file, simplified version of do_directory_merge(), which see for parameter descriptions. @@ -3868,6 +4037,9 @@ merge source are historically related (ancestors, uncles, second cousins thrice removed, etc...). (This is used to simulate the history checks that the repository logic does in the directory case.) + + Note: MERGE_B->RA_SESSION1 must be associated with URL1 and + MERGE_B->RA_SESSION2 with URL2. */ static svn_error_t * do_file_merge(const char *url1, @@ -3900,6 +4072,7 @@ svn_boolean_t is_rollback = (revision1 > revision2); const char *primary_url = is_rollback ? url1 : url2; svn_boolean_t honor_mergeinfo, record_mergeinfo; + svn_mergeinfo_t implicit_mergeinfo; mergeinfo_behavior(&honor_mergeinfo, &record_mergeinfo, merge_b); @@ -3922,32 +4095,30 @@ if (honor_mergeinfo) { const char *source_root_url; - svn_mergeinfo_t implicit_mergeinfo; - - + SVN_ERR(svn_ra_get_repos_root2(merge_b->ra_session1, &source_root_url, pool)); SVN_ERR(svn_client__path_relative_to_root(&mergeinfo_path, primary_url, source_root_url, TRUE, NULL, NULL, pool)); + /* Fetch mergeinfo (temporarily reparenting ra_session1 to + working copy target URL). */ + SVN_ERR(svn_ra_reparent(merge_b->ra_session1, entry->url, pool)); + SVN_ERR(get_full_mergeinfo(&target_mergeinfo, &implicit_mergeinfo, + entry, &indirect, svn_mergeinfo_inherited, + merge_b->ra_session1, target_wcpath, + MAX(revision1, revision2), + MIN(revision1, revision2), + adm_access, ctx, pool)); + + SVN_ERR(svn_ra_reparent(merge_b->ra_session1, url1, pool)); + /* Calculate remaining merges unless this is a record only merge. In that case the remaining range is the whole range described by REVISION1:REVISION2. */ if (!merge_b->record_only) { - /* Fetch mergeinfo (temporarily reparenting ra_session1 to - working copy target URL). */ - SVN_ERR(svn_ra_reparent(merge_b->ra_session1, entry->url, pool)); - SVN_ERR(get_full_mergeinfo(&target_mergeinfo, &implicit_mergeinfo, - entry, &indirect, svn_mergeinfo_inherited, - merge_b->ra_session1, target_wcpath, - MAX(revision1, revision2), - MIN(revision1, revision2), - adm_access, ctx, pool)); - - SVN_ERR(svn_ra_reparent(merge_b->ra_session1, url1, pool)); - SVN_ERR(calculate_remaining_ranges(&remaining_ranges, source_root_url, url1, revision1, url2, revision2, @@ -3969,16 +4140,38 @@ if (!merge_b->record_only) { - for (i = 0; i < remaining_ranges->nelts; i++) + apr_array_header_t *ranges_to_merge = remaining_ranges; + + /* If we have ancestrally related sources and more than one + range to merge, eliminate no-op ranges before going through + the effort of downloading the many copies of the file + required to do these merges (two copies per range). */ + if (merge_b->sources_ancestral && (remaining_ranges->nelts > 1)) { + const char *old_sess_url = NULL; + SVN_ERR(svn_client__ensure_ra_session_url(&old_sess_url, + merge_b->ra_session1, + primary_url, subpool)); + SVN_ERR(remove_noop_merge_ranges(&ranges_to_merge, + merge_b->ra_session1, + remaining_ranges, subpool)); + if (old_sess_url) + SVN_ERR(svn_ra_reparent(merge_b->ra_session1, old_sess_url, + subpool)); + svn_pool_clear(subpool); + } + + for (i = 0; i < ranges_to_merge->nelts; i++) + { svn_wc_notify_t *n; svn_boolean_t header_sent = FALSE; svn_error_t *err = SVN_NO_ERROR; + svn_ra_session_t *ra_session1, *ra_session2; /* When using this merge range, account for the exclusivity of its low value (which is indicated by this operation being a merge vs. revert). */ - svn_merge_range_t *r = APR_ARRAY_IDX(remaining_ranges, i, + svn_merge_range_t *r = APR_ARRAY_IDX(ranges_to_merge, i, svn_merge_range_t *); svn_pool_clear(subpool); @@ -3991,12 +4184,31 @@ if (merge_b->sources_ancestral) n->merge_range = r; + /* Issue #3174: If we are honoring mergeinfo, then URL1, URL2, + REVISION1, and REVISION2 meet the conditions described in + 'MERGEINFO MERGE SOURCE NORMALIZATION'. This means that + URL1@REVISION1 may be the copy source of URL2@REVISION2. + If this is the case, then URL1 != URL2. Since + MERGE_B->RA_SESSION1 is always opened with URL1, the only time + we can safely call single_file_merge_get_file() with that RA + session is for REVISION1 (or REVISION2 if this is a reverse + merge). */ + ra_session1 = merge_b->ra_session1; + ra_session2 = merge_b->ra_session2; + if (honor_mergeinfo && strcmp(url1, url2) != 0) + { + if (!is_rollback && r->start != revision1) + ra_session1 = ra_session2; /* Use URL2's RA session. */ + else if (is_rollback && r->end != revision2) + ra_session2 = ra_session1; /* Use URL1's RA session. */ + } + /* While we currently don't allow it, in theory we could be fetching two fulltexts from two different repositories here. */ - SVN_ERR(single_file_merge_get_file(&tmpfile1, merge_b->ra_session1, + SVN_ERR(single_file_merge_get_file(&tmpfile1, ra_session1, &props1, r->start, target_wcpath, subpool)); - SVN_ERR(single_file_merge_get_file(&tmpfile2, merge_b->ra_session2, + SVN_ERR(single_file_merge_get_file(&tmpfile2, ra_session2, &props2, r->end, target_wcpath, subpool)); @@ -4078,7 +4290,7 @@ return err; svn_error_clear(err); - if ((i < (remaining_ranges->nelts - 1)) + if ((i < (ranges_to_merge->nelts - 1)) && is_path_conflicted_by_merge(merge_b)) { conflicted_range = r; @@ -4088,24 +4300,41 @@ } /* !merge_b->record_only */ /* Record updated WC mergeinfo to account for our new merges, minus - any unresolved conflicts and skips. */ + any unresolved conflicts and skips. We use the original + REMAINING_RANGES here instead of the possibly-pared-down + RANGES_TO_MERGE because we want to record all the requested + merge ranges, include the noop ones. */ if (record_mergeinfo && remaining_ranges->nelts) { apr_hash_t *merges; - SVN_ERR(determine_merges_performed(&merges, target_wcpath, - &range, svn_depth_infinity, - adm_access, notify_b, merge_b, - subpool)); - /* If merge target has indirect mergeinfo set it before - recording the first merge range. */ - if (indirect) - SVN_ERR(svn_client__record_wc_mergeinfo(target_wcpath, - target_mergeinfo, - adm_access, subpool)); + apr_array_header_t *filtered_rangelist; + + /* Filter any ranges from TARGET_WCPATH's own history, there is no + need to record this explicitly in mergeinfo, it is already part + of TARGET_WCPATH's natural history (implicit mergeinfo). */ + SVN_ERR(filter_natural_history_from_mergeinfo(&filtered_rangelist, + mergeinfo_path, + implicit_mergeinfo, + &range, subpool)); + + if (filtered_rangelist->nelts) + { + SVN_ERR(determine_merges_performed(&merges, target_wcpath, + filtered_rangelist, + svn_depth_infinity, + adm_access, notify_b, + merge_b, subpool)); + /* If merge target has indirect mergeinfo set it before + recording the first merge range. */ + if (indirect) + SVN_ERR(svn_client__record_wc_mergeinfo(target_wcpath, + target_mergeinfo, + adm_access, subpool)); - SVN_ERR(update_wc_mergeinfo(target_wcpath, entry, mergeinfo_path, - merges, is_rollback, adm_access, - ctx, subpool)); + SVN_ERR(update_wc_mergeinfo(target_wcpath, entry, mergeinfo_path, + merges, is_rollback, adm_access, + ctx, subpool)); + } } svn_pool_destroy(subpool); @@ -4258,6 +4487,11 @@ ra_session, mergeinfo_path, adm_access, merge_b)); + /* Always start with a range which describes our most inclusive merge. */ + range.start = revision1; + range.end = revision2; + range.inheritable = inheritable; + if (honor_mergeinfo && !merge_b->record_only) { svn_revnum_t start_rev, end_rev; @@ -4267,119 +4501,116 @@ end revisions. */ start_rev = get_most_inclusive_start_rev(children_with_mergeinfo, is_rollback); - if (start_rev == SVN_INVALID_REVNUM) - start_rev = revision1; + + /* Is there anything to merge? */ + if (SVN_IS_VALID_REVNUM(start_rev)) + { + range.start = start_rev; + end_rev = get_youngest_end_rev(children_with_mergeinfo, is_rollback); - end_rev = get_youngest_end_rev(children_with_mergeinfo, is_rollback); + /* Build a range which describes our most inclusive merge. */ + range.start = start_rev; - /* Build a range which describes our most inclusive merge. */ - range.start = start_rev; - range.end = revision2; - range.inheritable = inheritable; + /* While END_REV is valid, do the following: - /* While END_REV is valid, do the following: + 1. slice each remaining ranges around this 'end_rev'. + 2. starting with START_REV, call + drive_merge_report_editor() on MERGE_B->target for + start_rev:end_rev. + 3. remove the first item from each remaining range. + 4. set START_REV=END_REV and pick the next END_REV. + 5. lather, rinse, repeat. + */ + iterpool = svn_pool_create(pool); + while (end_rev != SVN_INVALID_REVNUM) + { + svn_revnum_t next_end_rev; + const char *real_url1 = url1, *real_url2 = url2; + const char *old_sess1_url = NULL, *old_sess2_url = NULL; - 1. slice each remaining ranges around this 'end_rev'. - 2. starting with START_REV, call - drive_merge_report_editor() on MERGE_B->target for - start_rev:end_rev. - 3. remove the first item from each remaining range. - 4. set START_REV=END_REV and pick the next END_REV. - 5. lather, rinse, repeat. - */ - iterpool = svn_pool_create(pool); - while (end_rev != SVN_INVALID_REVNUM) - { - svn_revnum_t next_end_rev; - const char *real_url1 = url1, *real_url2 = url2; - const char *old_sess1_url = NULL, *old_sess2_url = NULL; + svn_pool_clear(iterpool); - svn_pool_clear(iterpool); + /* Use persistent pool while playing with remaining_ranges. */ + slice_remaining_ranges(children_with_mergeinfo, is_rollback, + end_rev, pool); + notify_b->cur_ancestor_index = -1; - /* Use persistent pool while playing with remaining_ranges. */ - slice_remaining_ranges(children_with_mergeinfo, is_rollback, - end_rev, pool); - notify_b->cur_ancestor_index = -1; + /* URL1@REVISION1 is a real location; URL2@REVISION2 is a + real location -- that much we know (thanks to the merge + source normalization code). But for revisions between + them, the URLs might differ. Here are the rules: - /* URL1@REVISION1 is a real location; URL2@REVISION2 is a - real location -- that much we know (thanks to the merge - source normalization code). But for revisions between - them, the URLs might differ. Here are the rules: + * If URL1 == URL2, then all URLs between REVISION1 and + REVISION2 also match URL1/URL2. - * If URL1 == URL2, then all URLs between REVISION1 and - REVISION2 also match URL1/URL2. + * If URL1 != URL2, then: - * If URL1 != URL2, then: + * If REVISION1 < REVISION2, only REVISION1 maps to + URL1. The revisions between REVISION1+1 and + REVISION2 (inclusive) map to URL2. - * If REVISION1 < REVISION2, only REVISION1 maps to - URL1. The revisions between REVISION1+1 and - REVISION2 (inclusive) map to URL2. - - * If REVISION1 > REVISION2, Only REVISION2 maps to - URL2. The revisions between REVISION1 and - REVISION2+1 (inclusive) map to URL1. - - We need to adjust our URLs accordingly, here. - */ - if (! same_urls) - { - if (is_rollback && (end_rev != revision2)) + * If REVISION1 > REVISION2, Only REVISION2 maps to + URL2. The revisions between REVISION1 and + REVISION2+1 (inclusive) map to URL1. + + We need to adjust our URLs accordingly, here. + */ + if (! same_urls) { - real_url2 = url1; - SVN_ERR(svn_client__ensure_ra_session_url - (&old_sess2_url, merge_b->ra_session2, - real_url2, iterpool)); + if (is_rollback && (end_rev != revision2)) + { + real_url2 = url1; + SVN_ERR(svn_client__ensure_ra_session_url + (&old_sess2_url, merge_b->ra_session2, + real_url2, iterpool)); + } + if ((! is_rollback) && (start_rev != revision1)) + { + real_url1 = url2; + SVN_ERR(svn_client__ensure_ra_session_url + (&old_sess1_url, merge_b->ra_session1, + real_url1, iterpool)); + } } - if ((! is_rollback) && (start_rev != revision1)) + SVN_ERR(drive_merge_report_editor(merge_b->target, + real_url1, start_rev, + real_url2, end_rev, + children_with_mergeinfo, + is_rollback, + depth, notify_b, adm_access, + &merge_callbacks, merge_b, + iterpool)); + if (old_sess1_url) + SVN_ERR(svn_ra_reparent(merge_b->ra_session1, + old_sess1_url, iterpool)); + if (old_sess2_url) + SVN_ERR(svn_ra_reparent(merge_b->ra_session2, + old_sess2_url, iterpool)); + + /* Prepare for the next iteration (if any). */ + remove_first_range_from_remaining_ranges( + end_rev, children_with_mergeinfo, pool); + next_end_rev = get_youngest_end_rev(children_with_mergeinfo, + is_rollback); + if ((next_end_rev != SVN_INVALID_REVNUM) + && is_path_conflicted_by_merge(merge_b)) { - real_url1 = url2; - SVN_ERR(svn_client__ensure_ra_session_url - (&old_sess1_url, merge_b->ra_session1, - real_url1, iterpool)); + svn_merge_range_t conflicted_range; + conflicted_range.start = start_rev; + conflicted_range.end = end_rev; + err = make_merge_conflict_error(merge_b->target, + &conflicted_range, pool); + range.end = end_rev; + break; } + start_rev = end_rev; + end_rev = next_end_rev; } - SVN_ERR(drive_merge_report_editor(merge_b->target, - real_url1, start_rev, real_url2, - end_rev, children_with_mergeinfo, - is_rollback, - depth, notify_b, adm_access, - &merge_callbacks, merge_b, - iterpool)); - if (old_sess1_url) - SVN_ERR(svn_ra_reparent(merge_b->ra_session1, - old_sess1_url, iterpool)); - if (old_sess2_url) - SVN_ERR(svn_ra_reparent(merge_b->ra_session2, - old_sess2_url, iterpool)); - - /* Prepare for the next iteration (if any). */ - remove_first_range_from_remaining_ranges(children_with_mergeinfo, - pool); - next_end_rev = get_youngest_end_rev(children_with_mergeinfo, - is_rollback); - if ((next_end_rev != SVN_INVALID_REVNUM) - && is_path_conflicted_by_merge(merge_b)) - { - svn_merge_range_t conflicted_range; - conflicted_range.start = start_rev; - conflicted_range.end = end_rev; - err = make_merge_conflict_error(merge_b->target, - &conflicted_range, pool); - range.end = end_rev; - break; - } - start_rev = end_rev; - end_rev = next_end_rev; + svn_pool_destroy(iterpool); } - svn_pool_destroy(iterpool); } else { - /* Build a range which describes our most inclusive merge. */ - range.start = revision1; - range.end = revision2; - range.inheritable = inheritable; - if (!merge_b->record_only) { /* Reset cur_ancestor_index to -1 so that subsequent cherry @@ -4405,6 +4636,10 @@ iterpool = svn_pool_create(pool); if (record_mergeinfo) { + apr_array_header_t *filtered_rangelist; + svn_client__merge_path_t *merge_target = + APR_ARRAY_IDX(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; @@ -4414,15 +4649,30 @@ calculate the merges performed. */ remove_absent_children(merge_b->target, children_with_mergeinfo, notify_b); - SVN_ERR(determine_merges_performed(&merges, merge_b->target, &range, - depth, adm_access, notify_b, merge_b, - iterpool)); - SVN_ERR(record_mergeinfo_on_merged_children(depth, adm_access, notify_b, - merge_b, iterpool)); - SVN_ERR(update_wc_mergeinfo(merge_b->target, parent_entry, - mergeinfo_path, merges, - is_rollback, adm_access, merge_b->ctx, - iterpool)); + + /* 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(record_mergeinfo_on_merged_children(depth, adm_access, + notify_b, merge_b, + iterpool)); + SVN_ERR(update_wc_mergeinfo(merge_b->target, parent_entry, + mergeinfo_path, merges, + is_rollback, adm_access, merge_b->ctx, + iterpool)); + } + for (i = 0; i < children_with_mergeinfo->nelts; i++) { const char *child_repos_path; @@ -4430,7 +4680,6 @@ const svn_wc_entry_t *child_entry; apr_array_header_t *child_merge_rangelist; apr_hash_t *child_merges; - svn_merge_range_t *child_merge_range; svn_client__merge_path_t *child = APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *); @@ -4449,19 +4698,31 @@ adm_access, FALSE, iterpool)); child_merges = apr_hash_make(iterpool); - child_merge_range = svn_merge_range_dup(&range, iterpool); - if (child_entry->kind == svn_node_file) - child_merge_range->inheritable = TRUE; + + /* 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 - child_merge_range->inheritable = - (!(child->missing_child) - && (depth == svn_depth_infinity + { + 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)); - child_merge_rangelist = - apr_array_make(iterpool, 1, - sizeof(child_merge_range)); - APR_ARRAY_PUSH(child_merge_rangelist, - svn_merge_range_t *) = child_merge_range; + } + } apr_hash_set(child_merges, child->path, APR_HASH_KEY_STRING, child_merge_rangelist); /* If merge target has indirect mergeinfo set it before @@ -4489,10 +4750,42 @@ merge_b, children_with_mergeinfo, i, iterpool)); + + /* Elide explicit subtree mergeinfo. */ if (i > 0) - SVN_ERR(svn_client__elide_mergeinfo(child->path, merge_b->target, - child_entry, adm_access, - merge_b->ctx, iterpool)); + { + 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(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 < children_with_mergeinfo->nelts; i++) */ /* If a path has an immediate parent with non-inheritable mergeinfo at --- subversion/libsvn_client/mergeinfo.c +++ subversion/libsvn_client/mergeinfo.c @@ -164,14 +164,14 @@ SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, entry, wcpath, pristine, adm_access, ctx, pool)); - - /* If WCPATH is switched, don't look any higher for inherited - mergeinfo. */ - SVN_ERR(svn_wc__path_switched(wcpath, &switched, entry, pool)); - if (switched) - break; } + /* If WCPATH is switched, don't look any higher for inherited + mergeinfo. */ + SVN_ERR(svn_wc__path_switched(wcpath, &switched, entry, pool)); + if (switched) + break; + /* Subsequent svn_wc_adm_access_t need to be opened with an absolute path so we can walk up and out of the WC if necessary. If we are using LIMIT_PATH it needs to @@ -725,57 +725,52 @@ { svn_mergeinfo_t target_mergeinfo; svn_mergeinfo_t mergeinfo = NULL; - svn_boolean_t inherited, switched; + svn_boolean_t inherited; const char *walk_path; - /* Check for second easy out: TARGET_WCPATH is switched. */ - SVN_ERR(svn_wc__path_switched(target_wcpath, &switched, entry, pool)); - if (!switched) - { - /* Get the TARGET_WCPATH's explicit mergeinfo. */ - SVN_ERR(svn_client__get_wc_mergeinfo(&target_mergeinfo, &inherited, - FALSE, svn_mergeinfo_inherited, - entry, target_wcpath, - wc_elision_limit_path - ? wc_elision_limit_path - : NULL, - &walk_path, adm_access, - ctx, pool)); + /* Get the TARGET_WCPATH's explicit mergeinfo. */ + SVN_ERR(svn_client__get_wc_mergeinfo(&target_mergeinfo, &inherited, + FALSE, svn_mergeinfo_inherited, + entry, target_wcpath, + wc_elision_limit_path + ? wc_elision_limit_path + : NULL, + &walk_path, adm_access, + ctx, pool)); - /* If TARGET_WCPATH has no explicit mergeinfo, there's nothing to - elide, we're done. */ - if (inherited || target_mergeinfo == NULL) - return SVN_NO_ERROR; + /* If TARGET_WCPATH has no explicit mergeinfo, there's nothing to + elide, we're done. */ + if (inherited || target_mergeinfo == NULL) + return SVN_NO_ERROR; - /* Get TARGET_WCPATH's inherited mergeinfo from the WC. */ - SVN_ERR(svn_client__get_wc_mergeinfo(&mergeinfo, &inherited, FALSE, - svn_mergeinfo_nearest_ancestor, - entry, target_wcpath, - wc_elision_limit_path - ? wc_elision_limit_path - : NULL, - &walk_path, adm_access, - ctx, pool)); + /* Get TARGET_WCPATH's inherited mergeinfo from the WC. */ + SVN_ERR(svn_client__get_wc_mergeinfo(&mergeinfo, &inherited, FALSE, + svn_mergeinfo_nearest_ancestor, + entry, target_wcpath, + wc_elision_limit_path + ? wc_elision_limit_path + : NULL, + &walk_path, adm_access, + ctx, pool)); - /* If TARGET_WCPATH inherited no mergeinfo from the WC and we are - not limiting our search to the working copy then check if it - inherits any from the repos. */ - if (!mergeinfo && !wc_elision_limit_path) - { - SVN_ERR(svn_client__get_wc_or_repos_mergeinfo - (&mergeinfo, entry, &inherited, TRUE, - svn_mergeinfo_nearest_ancestor, - NULL, target_wcpath, adm_access, ctx, pool)); - } + /* If TARGET_WCPATH inherited no mergeinfo from the WC and we are + not limiting our search to the working copy then check if it + inherits any from the repos. */ + if (!mergeinfo && !wc_elision_limit_path) + { + SVN_ERR(svn_client__get_wc_or_repos_mergeinfo + (&mergeinfo, entry, &inherited, TRUE, + svn_mergeinfo_nearest_ancestor, + NULL, target_wcpath, adm_access, ctx, pool)); + } - /* If there is nowhere to elide TARGET_WCPATH's mergeinfo to and - the elision is limited, then we are done.*/ - if (!mergeinfo && wc_elision_limit_path) - return SVN_NO_ERROR; + /* If there is nowhere to elide TARGET_WCPATH's mergeinfo to and + the elision is limited, then we are done.*/ + if (!mergeinfo && wc_elision_limit_path) + return SVN_NO_ERROR; - SVN_ERR(elide_mergeinfo(mergeinfo, target_mergeinfo, target_wcpath, - NULL, adm_access, pool)); - } + SVN_ERR(elide_mergeinfo(mergeinfo, target_mergeinfo, target_wcpath, + NULL, adm_access, pool)); } return SVN_NO_ERROR; } --- subversion/libsvn_client/mergeinfo.h +++ subversion/libsvn_client/mergeinfo.h @@ -51,6 +51,8 @@ apr_array_header_t *remaining_ranges; /* Per path remaining ranges list. */ svn_mergeinfo_t pre_merge_mergeinfo; /* mergeinfo on a path prior to a merge.*/ + svn_mergeinfo_t implicit_mergeinfo; /* Implicit mergeinfo on a path prior + to a merge.*/ svn_boolean_t indirect_mergeinfo; svn_boolean_t scheduled_for_deletion; /* PATH is scheduled for deletion. */ } svn_client__merge_path_t; @@ -72,7 +74,7 @@ inherited mergeinfo for WCPATH is retrieved. Don't look for inherited mergeinfo any higher than LIMIT_PATH - (ignored if NULL). + (ignored if NULL) or beyond any switched path. Set *WALKED_PATH to the path climbed from WCPATH to find inherited mergeinfo, or "" if none was found. (ignored if NULL). */ @@ -196,10 +198,10 @@ copy (or possibly repository) ancestor with equivalent mergeinfo. If WC_ELISION_LIMIT_PATH is NULL check up to the root of the working copy - for an elision destination, if none is found check the repository, - otherwise check as far as WC_ELISION_LIMIT_PATH within the working copy. - TARGET_PATH and WC_ELISION_LIMIT_PATH, if it exists, must both be absolute - or relative to the working directory. + or the nearest switched parent for an elision destination, if none is found + check the repository, otherwise check as far as WC_ELISION_LIMIT_PATH + within the working copy. TARGET_PATH and WC_ELISION_LIMIT_PATH, if it + exists, must both be absolute or relative to the working directory. Elision occurs if: --- subversion/libsvn_wc/merge.c +++ subversion/libsvn_wc/merge.c @@ -624,9 +624,18 @@ } else { - svn_boolean_t same; + svn_boolean_t same, special; + /* If 'special', then use the detranslated form of the + target file. This is so we don't try to follow symlinks, + but the same treatment is probably also appropriate for + whatever special file types we may invent in the future. */ + SVN_ERR(svn_wc__get_special(&special, merge_target, + adm_access, pool)); SVN_ERR(svn_io_files_contents_same_p(&same, result_target, - merge_target, pool)); + (special ? + tmp_target : + merge_target), + pool)); *merge_outcome = same ? svn_wc_merge_unchanged : svn_wc_merge_merged; }