Index: subversion/libsvn_client/merge.c =================================================================== --- subversion/libsvn_client/merge.c (revision 29014) +++ subversion/libsvn_client/merge.c (working copy) @@ -299,6 +299,217 @@ apr_hash_count(merge_b->conflicted_paths) > 0); } +/* Set *HONOR_MERGEINFO and *RECORD_MERGEINFO (if non-NULL) + appropriately for MERGE_B. */ +static APR_INLINE void +mergeinfo_behavior(svn_boolean_t *honor_mergeinfo, + svn_boolean_t *record_mergeinfo, + merge_cmd_baton_t *merge_b) +{ + if (honor_mergeinfo) + *honor_mergeinfo = (merge_b->mergeinfo_capable + && merge_b->sources_ancestral + && merge_b->same_repos + && (! merge_b->ignore_ancestry)); + + if (record_mergeinfo) + *record_mergeinfo = (merge_b->mergeinfo_capable + && merge_b->sources_ancestral + && merge_b->same_repos + && (! merge_b->dry_run)); +} + +/* Helper for merge_props_changed(). Filter out mergeinfo property additions + to PATH when those additions refer to the same line of history. + + *PROPS is an array of svn_prop_t structures representing regular properties + to be added to the working copy PATH. ADM_ACCESS and MERGE_B are cascaded + from merge_props_changed(). + + If mergeinfo is not being honored, do nothing. Otherwise examine the added + mergeinfo, looking at each range (or single rev) of each source path. If a + source_path/range refers to the same line of history as PATH (pegged at its + base revision), then filter out that range. If the entire rangelist for a + given path is filtered then filter out the path as well. Set outgoing + *PROPS to a shallow copy (allocated in POOL) of incoming *PROPS minus the + filtered self-referential mergeinfo. */ +static svn_error_t* +filter_self_referential_mergeinfo(apr_array_header_t **props, + const char *path, + merge_cmd_baton_t *merge_b, + svn_wc_adm_access_t *adm_access, + apr_pool_t *pool) +{ + svn_boolean_t honor_mergeinfo, record_mergeinfo; + + mergeinfo_behavior(&honor_mergeinfo, &record_mergeinfo, merge_b); + if (honor_mergeinfo) + { + int i; + apr_array_header_t *adjusted_props = + apr_array_make(pool, 1, sizeof(svn_prop_t)); + + for (i = 0; i < (*props)->nelts; ++i) + { + svn_prop_t *prop = &APR_ARRAY_IDX((*props), i, svn_prop_t); + + /* If this property isn't mergeinfo or is empty mergeinfo it + does not require any special handling. */ + if (strcmp(prop->name, SVN_PROP_MERGEINFO) != 0 + || !prop->value) + { + APR_ARRAY_PUSH(adjusted_props, svn_prop_t) = *prop; + } + else /* Property is non-empty mergeinfo, check if any self- + referential mergeinfo should be filtered out. */ + { + apr_hash_t *mergeinfo_catalog; + apr_hash_index_t *hi; + const char *target_url, *merge_source_root_url; + const svn_wc_entry_t *target_entry; + const char *original_ra_url; + + SVN_ERR(svn_ra_get_repos_root(merge_b->ra_session1, + &merge_source_root_url, + pool)); + + /* Get an entry for PATH so we can find it's base revision. */ + SVN_ERR(svn_wc__entry_versioned(&target_entry, path, + adm_access, FALSE, + pool)); + + /* Temporarily reparent our RA session to the merge + target's URL. */ + SVN_ERR(svn_client_url_from_path(&target_url, path, + pool)); + SVN_ERR(svn_ra_get_session_url(merge_b->ra_session1, + &original_ra_url, + merge_b->long_pool)); + SVN_ERR(svn_ra_reparent(merge_b->ra_session1, + target_url, + merge_b->long_pool)); + + /* Parse the incoming mergeinfo to allow easier meddling. */ + SVN_ERR(svn_mergeinfo_parse(&mergeinfo_catalog, + prop->value->data, pool)); + + for (hi = apr_hash_first(NULL, mergeinfo_catalog); + hi; hi = apr_hash_next(hi)) + { + int j; + const void *key; + void *value; + const char *source_path; + apr_array_header_t *rangelist; + apr_array_header_t *adjusted_rangelist = + apr_array_make(pool, 0, + sizeof(svn_merge_range_t *)); + + apr_hash_this(hi, &key, NULL, &value); + source_path = key; + rangelist = value; + + for (j = 0; j < rangelist->nelts; j++) + { + svn_error_t *err; + svn_merge_range_t *range = + APR_ARRAY_IDX(rangelist, j, svn_merge_range_t *); + + svn_opt_revision_t *start_revision, *end_revision; + const char *start_url, *end_url; + const char *merge_source_url = + svn_path_join(merge_source_root_url, + source_path + 1, pool); + svn_opt_revision_t peg_rev, rev1_opt, rev2_opt; + + peg_rev.kind = svn_opt_revision_number; + peg_rev.value.number = target_entry->revision; + rev1_opt.kind = svn_opt_revision_number; + rev1_opt.value.number = range->start; + + /* Because the merge source normalization code + ensures mergeinfo refers to real locations on + the same line of history, there's no need to + look at the whole range, just the start. */ + rev2_opt.kind = svn_opt_revision_unspecified; + + /* Check if PATH@TARGET_ENTRY->REVISION exists at + RANGE->START on the same line of history. */ + err = svn_client__repos_locations(&start_url, + &start_revision, + &end_url, + &end_revision, + merge_b->ra_session1, + target_url, + &peg_rev, + &rev1_opt, + &rev2_opt, + merge_b->ctx, + pool); + if (err) + { + if (err->apr_err == SVN_ERR_FS_NOT_FOUND + || err->apr_err == + SVN_ERR_CLIENT_UNRELATED_RESOURCES) + { + /* */ + svn_error_clear(err); + err = NULL; + APR_ARRAY_PUSH(adjusted_rangelist, + svn_merge_range_t *) = range; + } + else + { + return err; + } + } + else + { + /* PATH@TARGET_ENTRY->REVISION exists on the same + line of history at RANGE->START. But it might + have been called something different then, so + check if the URL it had then is the same as the + URL for the mergeinfo we are trying to add. If + it is the same we can filter it out. */ + if (strcmp(start_url, merge_source_url) != 0) + { + APR_ARRAY_PUSH(adjusted_rangelist, + svn_merge_range_t *) = range; + } + } + } /* for (j = 0; j < rangelist->nelts; j++) */ + + /* If only some of the ranges mapped from SOURCE_PATH were + filtered then create a new svn_prop_t to represent + this. */ + if (adjusted_rangelist->nelts) + { + svn_stringbuf_t *adjusted_rangelist_sb; + svn_prop_t *adjusted_prop = + apr_pcalloc(pool, sizeof(*adjusted_prop)); + + SVN_ERR(svn_rangelist_to_stringbuf( + &adjusted_rangelist_sb, adjusted_rangelist, + pool)); + adjusted_prop->name = SVN_PROP_MERGEINFO; + adjusted_prop->value = svn_string_create( + apr_pstrcat(pool, source_path, ":", + adjusted_rangelist_sb->data, NULL), + pool); + APR_ARRAY_PUSH(adjusted_props, svn_prop_t) = + *adjusted_prop; + } + } /* mergeinfo_catalog hash iteration */ + /* Reparent the WB->RA_SESSION1 to its original URL. */ + SVN_ERR(svn_ra_reparent(merge_b->ra_session1, + original_ra_url, + merge_b->long_pool)); + } /* Property is non-empty mergeinfo. */ + } /* (i = 0; i < (*props)->nelts; ++i) */ + *props = adjusted_props; + } + return SVN_NO_ERROR; +} /* A svn_wc_diff_callbacks2_t function. Used for both file and directory property merges. */ static svn_error_t * @@ -332,6 +543,9 @@ TRUE, -1, ctx->cancel_func, ctx->cancel_baton, subpool)); + SVN_ERR(filter_self_referential_mergeinfo(&props, path, merge_b, + adm_access, subpool)); + err = svn_wc_merge_props2(state, path, adm_access, original_props, props, FALSE, merge_b->dry_run, ctx->conflict_func, ctx->conflict_baton, subpool); @@ -2068,26 +2282,6 @@ } } -/* Set *HONOR_MERGEINFO and *RECORD_MERGEINFO (if non-NULL) - appropriately for MERGE_B. */ -static APR_INLINE void -mergeinfo_behavior(svn_boolean_t *honor_mergeinfo, - svn_boolean_t *record_mergeinfo, - merge_cmd_baton_t *merge_b) -{ - if (honor_mergeinfo) - *honor_mergeinfo = (merge_b->mergeinfo_capable - && merge_b->sources_ancestral - && merge_b->same_repos - && (! merge_b->ignore_ancestry)); - - if (record_mergeinfo) - *record_mergeinfo = (merge_b->mergeinfo_capable - && merge_b->sources_ancestral - && merge_b->same_repos - && (! merge_b->dry_run)); -} - /* Sets up the diff editor report and drives it by properly negating subtree that could have a conflicting merge history. Index: subversion/tests/cmdline/merge_tests.py =================================================================== --- subversion/tests/cmdline/merge_tests.py (revision 29014) +++ subversion/tests/cmdline/merge_tests.py (working copy) @@ -9910,7 +9910,521 @@ ".*", ###TODO(reint): need a more specific check here None, None, None, None, True, False, '--reintegrate') +def self_referrential_mergeinfo(sbox): + "cyclic merges dont produce mergefinfo from self" + sbox.build() + wc_dir = sbox.wc_dir + wc_disk, wc_status = set_up_branch(sbox) + + # Some paths we'll care about + A_path = os.path.join(wc_dir, "A") + A_MOVED_path = os.path.join(wc_dir, "A_MOVED") + mu_path = os.path.join(wc_dir, "A", "mu") + mu_MOVED_path = os.path.join(wc_dir, "A_MOVED", "mu") + A_COPY_path = os.path.join(wc_dir, "A_COPY") + mu_COPY_path = os.path.join(wc_dir, "A_COPY", "mu") + + # Merge r5 from 'A' to 'A_COPY' and commit as r7. This creates mergeinfo + # of '/A:5' on 'A_COPY'. Then merge r7 from 'A_COPY' to 'A'. This attempts + # to add the mergeinfo '/A:5' to 'A', but since this is self referrential it + # should be filtered out, resulting in a no-op merge. + # + # Search for the comment entitled "The Merge Kluge" elsewhere in + # this file, to understand why we shorten and chdir() below. + short_A_COPY_path = shorten_path_kludge(A_COPY_path) + expected_output = wc.State(short_A_COPY_path, { + 'B/E/beta' : Item(status='U '), + }) + expected_A_COPY_status = wc.State(short_A_COPY_path, { + '' : Item(status=' M', wc_rev=2), + 'B' : Item(status=' ', wc_rev=2), + 'mu' : Item(status=' ', wc_rev=2), + 'B/E' : Item(status=' ', wc_rev=2), + 'B/E/alpha' : Item(status=' ', wc_rev=2), + 'B/E/beta' : Item(status='M ', wc_rev=2), + 'B/lambda' : Item(status=' ', wc_rev=2), + 'B/F' : Item(status=' ', wc_rev=2), + 'C' : Item(status=' ', wc_rev=2), + 'D' : Item(status=' ', wc_rev=2), + 'D/G' : Item(status=' ', wc_rev=2), + 'D/G/pi' : Item(status=' ', wc_rev=2), + 'D/G/rho' : Item(status=' ', wc_rev=2), + 'D/G/tau' : Item(status=' ', wc_rev=2), + 'D/gamma' : Item(status=' ', wc_rev=2), + 'D/H' : Item(status=' ', wc_rev=2), + 'D/H/chi' : Item(status=' ', wc_rev=2), + 'D/H/psi' : Item(status=' ', wc_rev=2), + 'D/H/omega' : Item(status=' ', wc_rev=2), + }) + expected_A_COPY_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGE_INFO : '/A:5'}), + 'B' : Item(), + 'mu' : Item("This is the file 'mu'.\n"), + 'B/E' : Item(), + 'B/E/alpha' : Item("This is the file 'alpha'.\n"), + 'B/E/beta' : Item("New content"), + 'B/lambda' : Item("This is the file 'lambda'.\n"), + 'B/F' : Item(), + 'C' : Item(), + 'D' : Item(), + 'D/G' : Item(), + 'D/G/pi' : Item("This is the file 'pi'.\n"), + 'D/G/rho' : Item("This is the file 'rho'.\n"), + 'D/G/tau' : Item("This is the file 'tau'.\n"), + 'D/gamma' : Item("This is the file 'gamma'.\n"), + 'D/H' : Item(), + 'D/H/chi' : Item("This is the file 'chi'.\n"), + 'D/H/psi' : Item("This is the file 'psi'.\n"), + 'D/H/omega' : Item("This is the file 'omega'.\n"), + }) + expected_A_COPY_skip = wc.State(short_A_COPY_path, { }) + saved_cwd = os.getcwd() + os.chdir(svntest.main.work_dir) + svntest.actions.run_and_verify_merge(short_A_COPY_path, '4', '5', + sbox.repo_url + \ + '/A', + expected_output, + expected_A_COPY_disk, + expected_A_COPY_status, + expected_A_COPY_skip, + None, None, None, None, + None, 1) + os.chdir(saved_cwd) + + # Commit the merge + expected_output = wc.State(wc_dir, { + 'A_COPY' : Item(verb='Sending'), + 'A_COPY/B/E/beta' : Item(verb='Sending'), + }) + wc_status.tweak('A_COPY', 'A_COPY/B/E/beta', wc_rev=7) + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + wc_status, + None, + wc_dir) + + # Merge r7 back to the original source. This attempts to add the + # mergeinfo '/A:5' to 'A', but since this represents the same line + # of history it would be self-referential and is filtered out. Since + # no other operative changes occur there is no output from the merge. + os.chdir(svntest.main.work_dir) + short_A_path = shorten_path_kludge(A_path) + svntest.actions.run_and_verify_svn(None, [], [], 'merge', '-c', '7', + sbox.repo_url + '/A_COPY', + short_A_path) + os.chdir(saved_cwd) + + # Now merge r3 from 'A' to 'A_COPY', make a text mod to 'A_COPY/mu' and + # commit both as r8. This results in mergeinfo of '/A:3,5' on 'A_COPY'. + # Then merge r8 from 'A_COPY' to 'A'. This attempts to add the mergeinfo + # '/A:3' to 'A', which is again filtered out, but the change to 'A/mu' + # makes the merge operative, so 'A' should get the mergeinfo '/A_COPY:8'. + os.chdir(svntest.main.work_dir) + expected_output = wc.State(short_A_COPY_path, { + 'D/H/psi' : Item(status='U '), + }) + expected_A_COPY_status.tweak('B/E/beta', status=' ', wc_rev=7) + expected_A_COPY_status.tweak('', status=' M', wc_rev=7) + expected_A_COPY_status.tweak('D/H/psi', status='M ') + expected_A_COPY_disk.tweak('D/H/psi', contents='New content') + expected_A_COPY_disk.tweak('', props={SVN_PROP_MERGE_INFO : '/A:3,5'}) + svntest.actions.run_and_verify_merge(short_A_COPY_path, '2', '3', + sbox.repo_url + \ + '/A', + expected_output, + expected_A_COPY_disk, + expected_A_COPY_status, + expected_A_COPY_skip, + None, None, None, None, + None, 1) + os.chdir(saved_cwd) + + # Change 'A_COPY/mu' + svntest.main.file_write(mu_COPY_path, "New content") + + # Commit r8 + expected_output = wc.State(wc_dir, { + 'A_COPY' : Item(verb='Sending'), + 'A_COPY/D/H/psi' : Item(verb='Sending'), + 'A_COPY/mu' : Item(verb='Sending'), + }) + wc_status.tweak('A_COPY', 'A_COPY/D/H/psi', 'A_COPY/mu', wc_rev=8) + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + wc_status, + None, + wc_dir) + + # Merge r8 back to the 'A' + short_A_path = shorten_path_kludge(A_path) + expected_output = wc.State(short_A_path, { + 'mu' : Item(status='U '), + }) + expected_A_status = wc.State(short_A_path, { + '' : Item(status=' M', wc_rev=1), + 'B' : Item(status=' ', wc_rev=1), + 'mu' : Item(status='M ', wc_rev=1), + 'B/E' : Item(status=' ', wc_rev=1), + 'B/E/alpha' : Item(status=' ', wc_rev=1), + 'B/E/beta' : Item(status=' ', wc_rev=5), + 'B/lambda' : Item(status=' ', wc_rev=1), + 'B/F' : Item(status=' ', wc_rev=1), + 'C' : Item(status=' ', wc_rev=1), + 'D' : Item(status=' ', wc_rev=1), + 'D/G' : Item(status=' ', wc_rev=1), + 'D/G/pi' : Item(status=' ', wc_rev=1), + 'D/G/rho' : Item(status=' ', wc_rev=4), + 'D/G/tau' : Item(status=' ', wc_rev=1), + 'D/gamma' : Item(status=' ', wc_rev=1), + 'D/H' : Item(status=' ', wc_rev=1), + 'D/H/chi' : Item(status=' ', wc_rev=1), + 'D/H/psi' : Item(status=' ', wc_rev=3), + 'D/H/omega' : Item(status=' ', wc_rev=6), + }) + expected_A_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGE_INFO : '/A_COPY:8'}), + 'B' : Item(), + 'mu' : Item("New content"), + 'B/E' : Item(), + 'B/E/alpha' : Item("This is the file 'alpha'.\n"), + 'B/E/beta' : Item("New content"), + 'B/lambda' : Item("This is the file 'lambda'.\n"), + 'B/F' : Item(), + 'C' : Item(), + 'D' : Item(), + 'D/G' : Item(), + 'D/G/pi' : Item("This is the file 'pi'.\n"), + 'D/G/rho' : Item("New content"), + 'D/G/tau' : Item("This is the file 'tau'.\n"), + 'D/gamma' : Item("This is the file 'gamma'.\n"), + 'D/H' : Item(), + 'D/H/chi' : Item("This is the file 'chi'.\n"), + 'D/H/psi' : Item("New content"), + 'D/H/omega' : Item("New content"), + }) + expected_A_skip = wc.State(short_A_path, {}) + os.chdir(svntest.main.work_dir) + svntest.actions.run_and_verify_merge(short_A_path, '7', '8', + sbox.repo_url + \ + '/A_COPY', + expected_output, + expected_A_disk, + expected_A_status, + expected_A_skip, + None, None, None, None, + None, 1) + os.chdir(saved_cwd) + + # Revert all local mods + svntest.actions.run_and_verify_svn(None, + ["Reverted '" + A_path + "'\n", + "Reverted '" + mu_path + "'\n"], + [], 'revert', '-R', wc_dir) + + # Move 'A' to 'A_MOVED' and once again merge r8 from 'A_COPY', this time + # to 'A_MOVED'. This attempts to add the mergeinfo '/A:3' to + # 'A_MOVED', but 'A_MOVED@3' is 'A', but this would be self referential + # mergeinfo so is filtered out, laving the only the mergeinfo created + # from the merge itself: '/A_COPY:8'. + svntest.actions.run_and_verify_svn(None, + ['\n', 'Committed revision 9.\n'], + [], 'move', + sbox.repo_url + '/A', + sbox.repo_url + '/A_MOVED', + '-m', 'Copy A to A_MOVED') + wc_status.remove('A', 'A/B', 'A/B/lambda', 'A/B/E', 'A/B/E/alpha', + 'A/B/E/beta', 'A/B/F', 'A/mu', 'A/C', 'A/D', 'A/D/gamma', 'A/D/G', + 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau', 'A/D/H', 'A/D/H/chi', + 'A/D/H/omega', 'A/D/H/psi') + wc_status.add({ + 'A_MOVED' : Item(), + 'A_MOVED/B' : Item(), + 'A_MOVED/B/lambda' : Item(), + 'A_MOVED/B/E' : Item(), + 'A_MOVED/B/E/alpha' : Item(), + 'A_MOVED/B/E/beta' : Item(), + 'A_MOVED/B/F' : Item(), + 'A_MOVED/mu' : Item(), + 'A_MOVED/C' : Item(), + 'A_MOVED/D' : Item(), + 'A_MOVED/D/gamma' : Item(), + 'A_MOVED/D/G' : Item(), + 'A_MOVED/D/G/pi' : Item(), + 'A_MOVED/D/G/rho' : Item(), + 'A_MOVED/D/G/tau' : Item(), + 'A_MOVED/D/H' : Item(), + 'A_MOVED/D/H/chi' : Item(), + 'A_MOVED/D/H/omega' : Item(), + 'A_MOVED/D/H/psi' : Item(), + }) + wc_status.tweak(wc_rev=9, status=' ') + wc_disk.remove('A', 'A/B', 'A/B/lambda', 'A/B/E', 'A/B/E/alpha', + 'A/B/E/beta', 'A/B/F', 'A/mu', 'A/C', 'A/D', 'A/D/gamma', + 'A/D/G', 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau', 'A/D/H', + 'A/D/H/chi', 'A/D/H/omega', 'A/D/H/psi' ) + wc_disk.add({ + 'A_MOVED' : Item(), + 'A_MOVED/B' : Item(), + 'A_MOVED/B/lambda' : Item("This is the file 'lambda'.\n"), + 'A_MOVED/B/E' : Item(), + 'A_MOVED/B/E/alpha' : Item("This is the file 'alpha'.\n"), + 'A_MOVED/B/E/beta' : Item("New content"), + 'A_MOVED/B/F' : Item(), + 'A_MOVED/mu' : Item("This is the file 'mu'.\n"), + 'A_MOVED/C' : Item(), + 'A_MOVED/D' : Item(), + 'A_MOVED/D/gamma' : Item("This is the file 'gamma'.\n"), + 'A_MOVED/D/G' : Item(), + 'A_MOVED/D/G/pi' : Item("This is the file 'pi'.\n"), + 'A_MOVED/D/G/rho' : Item("New content"), + 'A_MOVED/D/G/tau' : Item("This is the file 'tau'.\n"), + 'A_MOVED/D/H' : Item(), + 'A_MOVED/D/H/chi' : Item("This is the file 'chi'.\n"), + 'A_MOVED/D/H/omega' : Item("New content"), + 'A_MOVED/D/H/psi' : Item("New content"), + }) + wc_disk.tweak('A_COPY/D/H/psi', 'A_COPY/mu', 'A_COPY/B/E/beta', + contents='New content') + wc_disk.tweak('A_COPY', props={SVN_PROP_MERGE_INFO : '/A:3,5'}) + expected_output = wc.State(wc_dir, { + 'A' : Item(status='D '), + 'A_MOVED' : Item(status='A '), + 'A_MOVED/B' : Item(status='A '), + 'A_MOVED/B/lambda' : Item(status='A '), + 'A_MOVED/B/E' : Item(status='A '), + 'A_MOVED/B/E/alpha' : Item(status='A '), + 'A_MOVED/B/E/beta' : Item(status='A '), + 'A_MOVED/B/F' : Item(status='A '), + 'A_MOVED/mu' : Item(status='A '), + 'A_MOVED/C' : Item(status='A '), + 'A_MOVED/D' : Item(status='A '), + 'A_MOVED/D/gamma' : Item(status='A '), + 'A_MOVED/D/G' : Item(status='A '), + 'A_MOVED/D/G/pi' : Item(status='A '), + 'A_MOVED/D/G/rho' : Item(status='A '), + 'A_MOVED/D/G/tau' : Item(status='A '), + 'A_MOVED/D/H' : Item(status='A '), + 'A_MOVED/D/H/chi' : Item(status='A '), + 'A_MOVED/D/H/omega' : Item(status='A '), + 'A_MOVED/D/H/psi' : Item(status='A ') + }) + svntest.actions.run_and_verify_update(wc_dir, + expected_output, + wc_disk, + wc_status, + None, None, None, None, None, + True) + short_A_MOVED_path = shorten_path_kludge(A_MOVED_path) + expected_output = wc.State(short_A_MOVED_path, { + 'mu' : Item(status='U '), + }) + expected_A_status = wc.State(short_A_MOVED_path, { + '' : Item(status=' M', wc_rev=9), + 'B' : Item(status=' ', wc_rev=9), + 'mu' : Item(status='M ', wc_rev=9), + 'B/E' : Item(status=' ', wc_rev=9), + 'B/E/alpha' : Item(status=' ', wc_rev=9), + 'B/E/beta' : Item(status=' ', wc_rev=9), + 'B/lambda' : Item(status=' ', wc_rev=9), + 'B/F' : Item(status=' ', wc_rev=9), + 'C' : Item(status=' ', wc_rev=9), + 'D' : Item(status=' ', wc_rev=9), + 'D/G' : Item(status=' ', wc_rev=9), + 'D/G/pi' : Item(status=' ', wc_rev=9), + 'D/G/rho' : Item(status=' ', wc_rev=9), + 'D/G/tau' : Item(status=' ', wc_rev=9), + 'D/gamma' : Item(status=' ', wc_rev=9), + 'D/H' : Item(status=' ', wc_rev=9), + 'D/H/chi' : Item(status=' ', wc_rev=9), + 'D/H/psi' : Item(status=' ', wc_rev=9), + 'D/H/omega' : Item(status=' ', wc_rev=9), + }) + # We can reuse expected_A_disk from above without change. + os.chdir(svntest.main.work_dir) + svntest.actions.run_and_verify_merge(short_A_MOVED_path, '7', '8', + sbox.repo_url + \ + '/A_COPY', + expected_output, + expected_A_disk, + expected_A_status, + expected_A_skip, + None, None, None, None, + None, 1) + os.chdir(saved_cwd) + + # Revert all local mods + svntest.actions.run_and_verify_svn(None, + ["Reverted '" + A_MOVED_path + "'\n", + "Reverted '" + mu_MOVED_path + "'\n"], + [], 'revert', '-R', wc_dir) + + # Create a new 'A' unrelated to the old 'A' which was moved. Then merge + # r8 from 'A_COPY' to this new 'A'. Since the new 'A' shares no history + # with the mergeinfo 'A@3', the mergeinfo '/A:3' is added and when combined + # with the mergeinfo created from the merge should result in + # '/A:3\n/A_COPY:8' + # + # Create the new 'A' by exporting the old 'A@1'. + expected_output = svntest.verify.UnorderedOutput( + ["A " + os.path.join(wc_dir, "A") + "\n", + "A " + os.path.join(wc_dir, "A", "B") + "\n", + "A " + os.path.join(wc_dir, "A", "B", "lambda") + "\n", + "A " + os.path.join(wc_dir, "A", "B", "E") + "\n", + "A " + os.path.join(wc_dir, "A", "B", "E", "alpha") + "\n", + "A " + os.path.join(wc_dir, "A", "B", "E", "beta") + "\n", + "A " + os.path.join(wc_dir, "A", "B", "F") + "\n", + "A " + os.path.join(wc_dir, "A", "mu") + "\n", + "A " + os.path.join(wc_dir, "A", "C") + "\n", + "A " + os.path.join(wc_dir, "A", "D") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "gamma") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "G") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "G", "pi") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "G", "rho") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "G", "tau") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "H") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "H", "chi") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "H", "omega") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "H", "psi") + "\n", + "Exported revision 1.\n",] + ) + svntest.actions.run_and_verify_svn(None, expected_output, [], + 'export', sbox.repo_url + '/A@1', + A_path) + expected_output = svntest.verify.UnorderedOutput( + ["A " + os.path.join(wc_dir, "A") + "\n", + "A " + os.path.join(wc_dir, "A", "B") + "\n", + "A " + os.path.join(wc_dir, "A", "B", "lambda") + "\n", + "A " + os.path.join(wc_dir, "A", "B", "E") + "\n", + "A " + os.path.join(wc_dir, "A", "B", "E", "alpha") + "\n", + "A " + os.path.join(wc_dir, "A", "B", "E", "beta") + "\n", + "A " + os.path.join(wc_dir, "A", "B", "F") + "\n", + "A " + os.path.join(wc_dir, "A", "mu") + "\n", + "A " + os.path.join(wc_dir, "A", "C") + "\n", + "A " + os.path.join(wc_dir, "A", "D") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "gamma") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "G") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "G", "pi") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "G", "rho") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "G", "tau") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "H") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "H", "chi") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "H", "omega") + "\n", + "A " + os.path.join(wc_dir, "A", "D", "H", "psi") + "\n",] + ) + svntest.actions.run_and_verify_svn(None, expected_output, [], + 'add', A_path) + # Commit the new 'A' as r10 + expected_output = wc.State(wc_dir, { + 'A' : Item(verb='Adding'), + 'A/B' : Item(verb='Adding'), + 'A/mu' : Item(verb='Adding'), + 'A/B/E' : Item(verb='Adding'), + 'A/B/E/alpha' : Item(verb='Adding'), + 'A/B/E/beta' : Item(verb='Adding'), + 'A/B/lambda' : Item(verb='Adding'), + 'A/B/F' : Item(verb='Adding'), + 'A/C' : Item(verb='Adding'), + 'A/D' : Item(verb='Adding'), + 'A/D/G' : Item(verb='Adding'), + 'A/D/G/pi' : Item(verb='Adding'), + 'A/D/G/rho' : Item(verb='Adding'), + 'A/D/G/tau' : Item(verb='Adding'), + 'A/D/gamma' : Item(verb='Adding'), + 'A/D/H' : Item(verb='Adding'), + 'A/D/H/chi' : Item(verb='Adding'), + 'A/D/H/psi' : Item(verb='Adding'), + 'A/D/H/omega' : Item(verb='Adding'), + }) + wc_status.tweak(wc_rev=9) + wc_status.add({ + 'A' : Item(wc_rev=10), + 'A/B' : Item(wc_rev=10), + 'A/B/lambda' : Item(wc_rev=10), + 'A/B/E' : Item(wc_rev=10), + 'A/B/E/alpha' : Item(wc_rev=10), + 'A/B/E/beta' : Item(wc_rev=10), + 'A/B/F' : Item(wc_rev=10), + 'A/mu' : Item(wc_rev=10), + 'A/C' : Item(wc_rev=10), + 'A/D' : Item(wc_rev=10), + 'A/D/gamma' : Item(wc_rev=10), + 'A/D/G' : Item(wc_rev=10), + 'A/D/G/pi' : Item(wc_rev=10), + 'A/D/G/rho' : Item(wc_rev=10), + 'A/D/G/tau' : Item(wc_rev=10), + 'A/D/H' : Item(wc_rev=10), + 'A/D/H/chi' : Item(wc_rev=10), + 'A/D/H/omega' : Item(wc_rev=10), + 'A/D/H/psi' : Item(wc_rev=10), + }) + wc_status.tweak(status=' ') + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + wc_status, + None, + wc_dir) + + expected_output = wc.State(short_A_path, { + 'mu' : Item(status='U '), + 'D/H/psi' : Item(status='U '), + '' : Item(status=' U'), + }) + expected_A_status = wc.State(short_A_path, { + '' : Item(status=' M', wc_rev=10), + 'B' : Item(status=' ', wc_rev=10), + 'mu' : Item(status='M ', wc_rev=10), + 'B/E' : Item(status=' ', wc_rev=10), + 'B/E/alpha' : Item(status=' ', wc_rev=10), + 'B/E/beta' : Item(status=' ', wc_rev=10), + 'B/lambda' : Item(status=' ', wc_rev=10), + 'B/F' : Item(status=' ', wc_rev=10), + 'C' : Item(status=' ', wc_rev=10), + 'D' : Item(status=' ', wc_rev=10), + 'D/G' : Item(status=' ', wc_rev=10), + 'D/G/pi' : Item(status=' ', wc_rev=10), + 'D/G/rho' : Item(status=' ', wc_rev=10), + 'D/G/tau' : Item(status=' ', wc_rev=10), + 'D/gamma' : Item(status=' ', wc_rev=10), + 'D/H' : Item(status=' ', wc_rev=10), + 'D/H/chi' : Item(status=' ', wc_rev=10), + 'D/H/psi' : Item(status='M ', wc_rev=10), + 'D/H/omega' : Item(status=' ', wc_rev=10), + }) + expected_A_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGE_INFO : '/A:3\n/A_COPY:8\n'}), + 'B' : Item(), + 'mu' : Item("New content"), + 'B/E' : Item(), + 'B/E/alpha' : Item("This is the file 'alpha'.\n"), + 'B/E/beta' : Item("This is the file 'beta'.\n"), + 'B/lambda' : Item("This is the file 'lambda'.\n"), + 'B/F' : Item(), + 'C' : Item(), + 'D' : Item(), + 'D/G' : Item(), + 'D/G/pi' : Item("This is the file 'pi'.\n"), + 'D/G/rho' : Item("This is the file 'rho'.\n"), + 'D/G/tau' : Item("This is the file 'tau'.\n"), + 'D/gamma' : Item("This is the file 'gamma'.\n"), + 'D/H' : Item(), + 'D/H/chi' : Item("This is the file 'chi'.\n"), + 'D/H/psi' : Item("New content"), + 'D/H/omega' : Item("This is the file 'omega'.\n"), + }) + expected_A_skip = wc.State(short_A_path, {}) + os.chdir(svntest.main.work_dir) + svntest.actions.run_and_verify_merge(short_A_path, '7', '8', + sbox.repo_url + \ + '/A_COPY', + expected_output, + expected_A_disk, + expected_A_status, + expected_A_skip, + None, None, None, None, + None, 1) + os.chdir(saved_cwd) + ######################################################################## # Run the tests @@ -10005,6 +10519,7 @@ reintegrate_fail_on_switched_wc, reintegrate_fail_on_shallow_wc, XFail(reintegrate_fail_on_stale_source), + self_referrential_mergeinfo, ] if __name__ == '__main__':