diff options
author | James McCoy <jamessan@debian.org> | 2018-07-31 22:26:52 -0400 |
---|---|---|
committer | James McCoy <jamessan@debian.org> | 2018-07-31 22:26:52 -0400 |
commit | e20a507113ff1126aeb4a97b806390ea377fe292 (patch) | |
tree | 0260b3a40387d7f994fbadaf22f1e9d3c080b09f /subversion/tests/cmdline/merge_reintegrate_tests.py | |
parent | c64debffb81d2fa17e9a72af7199ccf88b3cc556 (diff) |
New upstream version 1.10.2
Diffstat (limited to 'subversion/tests/cmdline/merge_reintegrate_tests.py')
-rwxr-xr-x | subversion/tests/cmdline/merge_reintegrate_tests.py | 2893 |
1 files changed, 2893 insertions, 0 deletions
diff --git a/subversion/tests/cmdline/merge_reintegrate_tests.py b/subversion/tests/cmdline/merge_reintegrate_tests.py new file mode 100755 index 0000000..7a27373 --- /dev/null +++ b/subversion/tests/cmdline/merge_reintegrate_tests.py @@ -0,0 +1,2893 @@ +#!/usr/bin/env python +# +# merge_reintegrate_tests.py: testing merge --reintegrate +# +# Subversion is a tool for revision control. +# See http://subversion.apache.org for more information. +# +# ==================================================================== +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### + +# General modules +import shutil, sys, re, os +import time + +# Our testing module +import svntest +from svntest import main, wc, verify, actions + +# (abbreviation) +Item = wc.StateItem +Skip = svntest.testcase.Skip_deco +SkipUnless = svntest.testcase.SkipUnless_deco +XFail = svntest.testcase.XFail_deco +Issues = svntest.testcase.Issues_deco +Issue = svntest.testcase.Issue_deco +Wimp = svntest.testcase.Wimp_deco +exp_noop_up_out = svntest.actions.expected_noop_update_output + +from svntest.main import SVN_PROP_MERGEINFO +from svntest.main import server_has_mergeinfo +from svntest.mergetrees import set_up_branch +from svntest.mergetrees import expected_merge_output + +#---------------------------------------------------------------------- +def run_reintegrate(src_url, tgt_path): + """Run 'svn merge --reintegrate SRC_URL TGT_PATH'. Raise an error if + there is nothing on stdout, anything on stderr, or a non-zero exit + code. + """ + svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], + 'merge', '--reintegrate', + src_url, tgt_path) + +def run_reintegrate_expect_error(src_url, tgt_path, + expected_stdout, expected_stderr): + """Run 'svn merge --reintegrate SRC_URL TGT_PATH'. Raise an error + unless stdout and stderr both match and the exit code is non-zero. + Every line of stderr must match the regex EXPECTED_STDERR. + """ + expected_stderr += "|" + svntest.main.stack_trace_regexp + + # The actions.run_and_verify_* methods are happy if one line of the error + # matches the regex, but we want to check that every line matches. + # So we will pass the stderr to svntest.verify.verify_outputs() + # ourselves, but as the 'actual_stdout' argument, that way each line of + # error must match the regex. + exit_code, out, err = svntest.actions.run_and_verify_svn( + expected_stdout, svntest.verify.AnyOutput, + 'merge', '--reintegrate', + src_url, tgt_path) + assert exit_code + svntest.verify.verify_outputs( + "Reintegrate failed but not in the way expected", + err, None, + expected_stderr, None, + True) # Match *all* lines + +def run_and_verify_reintegrate(tgt_dir, src_url, + output_tree, + mergeinfo_output_tree, + elision_output_tree, + disk_tree, status_tree, skip_tree, + expected_stderr = [], + check_props = True, + dry_run = True): + """Run 'svn merge --reintegrate SRC_URL TGT_DIR'. Raise an error if + there is nothing on stdout, anything on stderr, or a non-zero exit + code, or if the expected ERROR_RE_STRING or any of the given expected + trees don't match. + """ + svntest.actions.run_and_verify_merge( + tgt_dir, None, None, src_url, None, + output_tree, mergeinfo_output_tree, elision_output_tree, + disk_tree, status_tree, skip_tree, + expected_stderr, check_props, dry_run, + '--reintegrate', tgt_dir) + + +#---------------------------------------------------------------------- +@SkipUnless(server_has_mergeinfo) +@Issue(3640) +def basic_reintegrate(sbox): + "basic merge --reintegrate support" + + # Also includes test for issue #3640 'moved target breaks reintegrate merge' + + # Make A_COPY branch in r2, and do a few more commits to A in r3-6. + sbox.build() + wc_dir = sbox.wc_dir + expected_disk, expected_status = set_up_branch(sbox) + + # Make a change on the branch, to A/mu. Commit in r7. + svntest.main.file_write(sbox.ospath('A_COPY/mu'), + "Changed on the branch.") + expected_output = wc.State(wc_dir, {'A_COPY/mu' : Item(verb='Sending')}) + expected_status.tweak('A_COPY/mu', wc_rev=7) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + expected_disk.tweak('A_COPY/mu', contents='Changed on the branch.') + + # Update the wcs. + expected_output = wc.State(wc_dir, {}) + expected_status.tweak(wc_rev='7') + svntest.actions.run_and_verify_update(wc_dir, expected_output, + expected_disk, expected_status, + check_props=True) + + # Merge from trunk to branch (ie, r3-6), using normal cherry-harvest. + A_COPY_path = sbox.ospath('A_COPY') + expected_output = wc.State(A_COPY_path, { + 'D/H/psi' : Item(status='U '), + 'D/G/rho' : Item(status='U '), + 'B/E/beta' : Item(status='U '), + 'D/H/omega' : Item(status='U '), + }) + expected_mergeinfo_output = wc.State(A_COPY_path, { + '' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_COPY_path, { + }) + k_expected_status = wc.State(A_COPY_path, { + "B" : Item(status=' ', wc_rev=7), + "B/lambda" : Item(status=' ', wc_rev=7), + "B/E" : Item(status=' ', wc_rev=7), + "B/E/alpha" : Item(status=' ', wc_rev=7), + "B/E/beta" : Item(status='M ', wc_rev=7), + "B/F" : Item(status=' ', wc_rev=7), + "mu" : Item(status=' ', wc_rev=7), + "C" : Item(status=' ', wc_rev=7), + "D" : Item(status=' ', wc_rev=7), + "D/gamma" : Item(status=' ', wc_rev=7), + "D/G" : Item(status=' ', wc_rev=7), + "D/G/pi" : Item(status=' ', wc_rev=7), + "D/G/rho" : Item(status='M ', wc_rev=7), + "D/G/tau" : Item(status=' ', wc_rev=7), + "D/H" : Item(status=' ', wc_rev=7), + "D/H/chi" : Item(status=' ', wc_rev=7), + "D/H/omega" : Item(status='M ', wc_rev=7), + "D/H/psi" : Item(status='M ', wc_rev=7), + "" : Item(status=' M', wc_rev=7), + }) + k_expected_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-7'}), + 'B' : Item(), + 'B/lambda' : Item("This is the file 'lambda'.\n"), + 'B/E' : Item(), + 'B/E/alpha' : Item("This is the file 'alpha'.\n"), + 'B/E/beta' : Item("New content"), + 'B/F' : Item(), + 'mu' : Item("Changed on the branch."), + 'C' : Item(), + 'D' : Item(), + 'D/gamma' : Item("This is the file 'gamma'.\n"), + '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/H' : Item(), + 'D/H/chi' : Item("This is the file 'chi'.\n"), + 'D/H/omega' : Item("New content"), + 'D/H/psi' : Item("New content"), + }) + expected_skip = wc.State(A_COPY_path, {}) + svntest.actions.run_and_verify_merge(A_COPY_path, None, None, + sbox.repo_url + '/A', None, + expected_output, + expected_mergeinfo_output, + expected_elision_output, + k_expected_disk, + k_expected_status, + expected_skip, + [], True) + expected_disk.tweak('A_COPY', props={SVN_PROP_MERGEINFO: '/A:2-7'}) + expected_disk.tweak('A_COPY/B/E/beta', contents="New content") + expected_disk.tweak('A_COPY/D/G/rho', contents="New content") + expected_disk.tweak('A_COPY/D/H/omega', contents="New content") + expected_disk.tweak('A_COPY/D/H/psi', contents="New content") + + # Commit the merge to branch (r8). + expected_output = wc.State(wc_dir, { + 'A_COPY/D/H/psi' : Item(verb='Sending'), + 'A_COPY/D/G/rho' : Item(verb='Sending'), + 'A_COPY/B/E/beta' : Item(verb='Sending'), + 'A_COPY/D/H/omega' : Item(verb='Sending'), + 'A_COPY' : Item(verb='Sending'), + }) + expected_status.tweak('A_COPY', 'A_COPY/D/H/psi', 'A_COPY/D/G/rho', + 'A_COPY/B/E/beta', 'A_COPY/D/H/omega', wc_rev=8) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + + # Update the wcs again. + expected_output = wc.State(wc_dir, {}) + expected_status.tweak(wc_rev='8') + svntest.actions.run_and_verify_update(wc_dir, expected_output, + expected_disk, expected_status, + check_props=True) + + + # *finally*, actually run merge --reintegrate in trunk with the + # branch URL. This should bring in the mu change and the tauprime + # change. + A_path = sbox.ospath('A') + expected_output = wc.State(A_path, { + 'mu' : Item(status='U '), + }) + expected_mergeinfo_output = wc.State(A_path, { + '' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_path, { + }) + k_expected_status = wc.State(A_path, { + "B" : Item(status=' ', wc_rev=8), + "B/lambda" : Item(status=' ', wc_rev=8), + "B/E" : Item(status=' ', wc_rev=8), + "B/E/alpha" : Item(status=' ', wc_rev=8), + "B/E/beta" : Item(status=' ', wc_rev=8), + "B/F" : Item(status=' ', wc_rev=8), + "mu" : Item(status='M ', wc_rev=8), + "C" : Item(status=' ', wc_rev=8), + "D" : Item(status=' ', wc_rev=8), + "D/gamma" : Item(status=' ', wc_rev=8), + "D/G" : Item(status=' ', wc_rev=8), + "D/G/pi" : Item(status=' ', wc_rev=8), + "D/G/rho" : Item(status=' ', wc_rev=8), + "D/G/tau" : Item(status=' ', wc_rev=8), + "D/H" : Item(status=' ', wc_rev=8), + "D/H/chi" : Item(status=' ', wc_rev=8), + "D/H/omega" : Item(status=' ', wc_rev=8), + "D/H/psi" : Item(status=' ', wc_rev=8), + "" : Item(status=' M', wc_rev=8), + }) + k_expected_disk.tweak('', props={SVN_PROP_MERGEINFO : '/A_COPY:2-8'}) + expected_skip = wc.State(A_path, {}) + run_and_verify_reintegrate(A_path, + sbox.repo_url + '/A_COPY', + expected_output, + expected_mergeinfo_output, + expected_elision_output, + k_expected_disk, + k_expected_status, + expected_skip, + [], True, True) + + # Test issue #3640: + # + # Revert the merge then move A to A_MOVED in r9. Repeat the merge, but + # targeting A_MOVED this time. This should work with almost the same + # results. The only differences being the inclusion of r9 in the + # mergeinfo and the A-->A_MOVED path difference. + svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) + svntest.actions.run_and_verify_svn(['Committing transaction...\n', + 'Committed revision 9.\n'], + [], 'move', + sbox.repo_url + '/A', + sbox.repo_url + '/A_MOVED', + '-m', 'Copy A to A_MOVED') + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + A_MOVED_path = sbox.ospath('A_MOVED') + expected_output = wc.State(A_MOVED_path, { + 'mu' : Item(status='U '), + }) + expected_mergeinfo_output = wc.State(A_MOVED_path, { + '' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_MOVED_path, { + }) + expected_status = wc.State(A_MOVED_path, { + "B" : Item(status=' '), + "B/lambda" : Item(status=' '), + "B/E" : Item(status=' '), + "B/E/alpha" : Item(status=' '), + "B/E/beta" : Item(status=' '), + "B/F" : Item(status=' '), + "mu" : Item(status='M '), + "C" : Item(status=' '), + "D" : Item(status=' '), + "D/gamma" : Item(status=' '), + "D/G" : Item(status=' '), + "D/G/pi" : Item(status=' '), + "D/G/rho" : Item(status=' '), + "D/G/tau" : Item(status=' '), + "D/H" : Item(status=' '), + "D/H/chi" : Item(status=' '), + "D/H/omega" : Item(status=' '), + "D/H/psi" : Item(status=' '), + "" : Item(status=' M'), + }) + expected_status.tweak(wc_rev=9) + k_expected_disk.tweak('', props={SVN_PROP_MERGEINFO : '/A_COPY:2-9'}) + expected_skip = wc.State(A_MOVED_path, {}) + run_and_verify_reintegrate(A_MOVED_path, + sbox.repo_url + '/A_COPY', + expected_output, + expected_mergeinfo_output, + expected_elision_output, + k_expected_disk, + expected_status, + expected_skip, + [], True, True) + +#---------------------------------------------------------------------- +@SkipUnless(server_has_mergeinfo) +def reintegrate_with_rename(sbox): + "merge --reintegrate with renamed file on branch" + + # Make A_COPY branch in r2, and do a few more commits to A in r3-6. + sbox.build() + wc_dir = sbox.wc_dir + expected_disk, expected_status = set_up_branch(sbox) + + # Make a change on the branch, to A/mu. Commit in r7. + svntest.main.file_write(sbox.ospath('A_COPY/mu'), + "Changed on the branch.") + expected_output = wc.State(wc_dir, {'A_COPY/mu' : Item(verb='Sending')}) + expected_status.tweak('A_COPY/mu', wc_rev=7) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + expected_disk.tweak('A_COPY/mu', contents='Changed on the branch.') + + # Update the wcs. + expected_output = wc.State(wc_dir, {}) + expected_status.tweak(wc_rev='7') + svntest.actions.run_and_verify_update(wc_dir, expected_output, + expected_disk, expected_status, + check_props=True) + + # Merge from trunk to branch (ie, r3-6), using normal cherry-harvest. + A_COPY_path = sbox.ospath('A_COPY') + expected_output = wc.State(A_COPY_path, { + 'D/H/psi' : Item(status='U '), + 'D/G/rho' : Item(status='U '), + 'B/E/beta' : Item(status='U '), + 'D/H/omega' : Item(status='U '), + }) + expected_mergeinfo_output = wc.State(A_COPY_path, { + '' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_COPY_path, { + }) + k_expected_status = wc.State(A_COPY_path, { + "B" : Item(status=' ', wc_rev=7), + "B/lambda" : Item(status=' ', wc_rev=7), + "B/E" : Item(status=' ', wc_rev=7), + "B/E/alpha" : Item(status=' ', wc_rev=7), + "B/E/beta" : Item(status='M ', wc_rev=7), + "B/F" : Item(status=' ', wc_rev=7), + "mu" : Item(status=' ', wc_rev=7), + "C" : Item(status=' ', wc_rev=7), + "D" : Item(status=' ', wc_rev=7), + "D/gamma" : Item(status=' ', wc_rev=7), + "D/G" : Item(status=' ', wc_rev=7), + "D/G/pi" : Item(status=' ', wc_rev=7), + "D/G/rho" : Item(status='M ', wc_rev=7), + "D/G/tau" : Item(status=' ', wc_rev=7), + "D/H" : Item(status=' ', wc_rev=7), + "D/H/chi" : Item(status=' ', wc_rev=7), + "D/H/omega" : Item(status='M ', wc_rev=7), + "D/H/psi" : Item(status='M ', wc_rev=7), + "" : Item(status=' M', wc_rev=7), + }) + k_expected_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-7'}), + 'B' : Item(), + 'B/lambda' : Item("This is the file 'lambda'.\n"), + 'B/E' : Item(), + 'B/E/alpha' : Item("This is the file 'alpha'.\n"), + 'B/E/beta' : Item("New content"), + 'B/F' : Item(), + 'mu' : Item("Changed on the branch."), + 'C' : Item(), + 'D' : Item(), + 'D/gamma' : Item("This is the file 'gamma'.\n"), + '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/H' : Item(), + 'D/H/chi' : Item("This is the file 'chi'.\n"), + 'D/H/omega' : Item("New content"), + 'D/H/psi' : Item("New content"), + }) + expected_skip = wc.State(A_COPY_path, {}) + svntest.actions.run_and_verify_merge(A_COPY_path, None, None, + sbox.repo_url + '/A', None, + expected_output, + expected_mergeinfo_output, + expected_elision_output, + k_expected_disk, + k_expected_status, + expected_skip, + [], True) + expected_disk.tweak('A_COPY', props={SVN_PROP_MERGEINFO: '/A:2-7'}) + expected_disk.tweak('A_COPY/B/E/beta', contents="New content") + expected_disk.tweak('A_COPY/D/G/rho', contents="New content") + expected_disk.tweak('A_COPY/D/H/omega', contents="New content") + expected_disk.tweak('A_COPY/D/H/psi', contents="New content") + + # Commit the merge to branch (r8). + expected_output = wc.State(wc_dir, { + 'A_COPY/D/H/psi' : Item(verb='Sending'), + 'A_COPY/D/G/rho' : Item(verb='Sending'), + 'A_COPY/B/E/beta' : Item(verb='Sending'), + 'A_COPY/D/H/omega' : Item(verb='Sending'), + 'A_COPY' : Item(verb='Sending'), + }) + expected_status.tweak('A_COPY', 'A_COPY/D/H/psi', 'A_COPY/D/G/rho', + 'A_COPY/B/E/beta', 'A_COPY/D/H/omega', wc_rev=8) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + + + # Update the wcs again. + # + # Note: this update had to be added because of r869016 (which was + # merged into the reintegrate branch in r869021). Without this + # update, the mergeinfo will not be inherited properly as part of + # the 'svn cp tau tauprime' step, and later (during the post-commit + # update, with the new expected_disk) we'll get an error like this: + # + # ============================================================= + # Expected 'tauprime' and actual 'tauprime' in disk tree are different! + # ============================================================= + # EXPECTED NODE TO BE: + # ============================================================= + # * Node name: tauprime + # Path: A_COPY/D/G/tauprime + # Contents: This is the file 'tau'. + # + # Properties: {'svn:mergeinfo': '/A/D/G/tau:2-7'} + # Attributes: {} + # Children: N/A (node is a file) + # ============================================================= + # ACTUAL NODE FOUND: + # ============================================================= + # * Node name: tauprime + # Path: G/tauprime + # Contents: This is the file 'tau'. + # + # Properties: {'svn:mergeinfo': ''} + # Attributes: {} + # Children: N/A (node is a file) + # + expected_output = wc.State(wc_dir, {}) + expected_status.tweak(wc_rev='8') + svntest.actions.run_and_verify_update(wc_dir, expected_output, + expected_disk, expected_status, + check_props=True) + + # Make another change on the branch: copy tau to tauprime. Commit + # in r9. + svntest.actions.run_and_verify_svn(None, [], 'cp', + sbox.repo_url + '/A_COPY/D/G/tau', + sbox.repo_url + '/A_COPY/D/G/tauprime', + '-m', + 'Repos to repos copy of tau to tauprime') + + # Update the trunk (well, the whole wc) to get the copy above and since + # reintegrate really wants a clean wc. + expected_output = wc.State(wc_dir, { + 'A_COPY/D/G/tauprime' : Item(verb='Adding') + }) + expected_output = wc.State(A_COPY_path, { + 'D/G/tauprime' : Item(status='A '), + }) + expected_status.add({'A_COPY/D/G/tauprime': Item(status=' ', wc_rev=9)}) + expected_disk.add({ + 'A_COPY/D/G/tauprime' : Item(props={SVN_PROP_MERGEINFO: '/A/D/G/tau:2-7'}, + contents="This is the file 'tau'.\n") + }) + expected_status.tweak(wc_rev='9') + svntest.actions.run_and_verify_update(wc_dir, expected_output, + expected_disk, expected_status, + check_props=True) + + # *finally*, actually run merge --reintegrate in trunk with the + # branch URL. This should bring in the mu change and the tauprime + # change. + A_path = sbox.ospath('A') + expected_output = wc.State(A_path, { + 'mu' : Item(status='U '), + 'D/G/tauprime' : Item(status='A '), + }) + expected_mergeinfo_output = wc.State(A_path, { + '' : Item(status=' U'), + 'D/G/tauprime' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_path, { + }) + k_expected_status = wc.State(A_path, { + "B" : Item(status=' ', wc_rev=9), + "B/lambda" : Item(status=' ', 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/F" : Item(status=' ', wc_rev=9), + "mu" : Item(status='M ', wc_rev=9), + "C" : Item(status=' ', wc_rev=9), + "D" : Item(status=' ', wc_rev=9), + "D/gamma" : 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/G/tauprime" : Item(status='A ', wc_rev='-', copied='+'), + "D/H" : Item(status=' ', wc_rev=9), + "D/H/chi" : Item(status=' ', wc_rev=9), + "D/H/omega" : Item(status=' ', wc_rev=9), + "D/H/psi" : Item(status=' ', wc_rev=9), + "" : Item(status=' M', wc_rev=9), + }) + k_expected_disk.tweak('', props={SVN_PROP_MERGEINFO : '/A_COPY:2-9'}) + k_expected_disk.add({ + 'D/G/tauprime' : Item(props={SVN_PROP_MERGEINFO : + '/A/D/G/tau:2-7\n/A_COPY/D/G/tauprime:9'}, + contents="This is the file 'tau'.\n") + }) + expected_skip = wc.State(A_path, {}) + run_and_verify_reintegrate(A_path, + sbox.repo_url + '/A_COPY', + expected_output, + expected_mergeinfo_output, + expected_elision_output, + k_expected_disk, + k_expected_status, + expected_skip, + [], True, True) + + # Finally, commit the result of the merge (r10). + expected_output = wc.State(wc_dir, { + 'A/D/G/tauprime' : Item(verb='Adding'), + 'A/mu' : Item(verb='Sending'), + 'A' : Item(verb='Sending'), + }) + expected_status.add({ + 'A/D/G/tauprime' : Item(status=' ', wc_rev=10), + }) + expected_status.tweak('A', 'A/mu', wc_rev=10) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + +#---------------------------------------------------------------------- +@SkipUnless(server_has_mergeinfo) +def reintegrate_branch_never_merged_to(sbox): + "merge --reintegrate on a never-updated branch" + + # Make A_COPY branch in r2, and do a few more commits to A in r3-6. + sbox.build() + wc_dir = sbox.wc_dir + expected_disk, expected_status = set_up_branch(sbox) + + # Make a change on the branch, to A_COPY/mu. Commit in r7. + svntest.main.file_write(sbox.ospath('A_COPY/mu'), + "Changed on the branch.") + expected_output = wc.State(wc_dir, {'A_COPY/mu' : Item(verb='Sending')}) + expected_status.tweak('A_COPY/mu', wc_rev=7) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + expected_disk.tweak('A_COPY/mu', contents='Changed on the branch.') + + # Update the wcs. + expected_output = wc.State(wc_dir, {}) + expected_status.tweak(wc_rev='7') + svntest.actions.run_and_verify_update(wc_dir, expected_output, + expected_disk, expected_status, + check_props=True) + + # Make another change on the branch: copy tau to tauprime. Commit + # in r8. + svntest.actions.run_and_verify_svn(None, [], 'cp', + os.path.join(wc_dir, 'A_COPY', 'D', 'G', + 'tau'), + os.path.join(wc_dir, 'A_COPY', 'D', 'G', + 'tauprime')) + expected_output = wc.State(wc_dir, { + 'A_COPY/D/G/tauprime' : Item(verb='Adding') + }) + expected_status.add({'A_COPY/D/G/tauprime': Item(status=' ', wc_rev=8)}) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + expected_disk.add({ + 'A_COPY/D/G/tauprime' : Item(contents="This is the file 'tau'.\n") + }) + + # Update the trunk (well, the whole wc) (since reintegrate really + # wants a clean wc). + expected_output = wc.State(wc_dir, {}) + expected_status.tweak(wc_rev='8') + svntest.actions.run_and_verify_update(wc_dir, expected_output, + expected_disk, expected_status, + check_props=True) + + # *finally*, actually run merge --reintegrate in trunk with the + # branch URL. This should bring in the mu change and the tauprime + # change. + A_path = sbox.ospath('A') + expected_output = wc.State(A_path, { + 'mu' : Item(status='U '), + 'D/G/tauprime' : Item(status='A '), + }) + expected_mergeinfo_output = wc.State(A_path, { + '' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_path, { + }) + k_expected_status = wc.State(A_path, { + "B" : Item(status=' ', wc_rev=8), + "B/lambda" : Item(status=' ', wc_rev=8), + "B/E" : Item(status=' ', wc_rev=8), + "B/E/alpha" : Item(status=' ', wc_rev=8), + "B/E/beta" : Item(status=' ', wc_rev=8), + "B/F" : Item(status=' ', wc_rev=8), + "mu" : Item(status='M ', wc_rev=8), + "C" : Item(status=' ', wc_rev=8), + "D" : Item(status=' ', wc_rev=8), + "D/gamma" : Item(status=' ', wc_rev=8), + "D/G" : Item(status=' ', wc_rev=8), + "D/G/pi" : Item(status=' ', wc_rev=8), + "D/G/rho" : Item(status=' ', wc_rev=8), + "D/G/tau" : Item(status=' ', wc_rev=8), + "D/G/tauprime" : Item(status='A ', wc_rev='-', copied='+'), + "D/H" : Item(status=' ', wc_rev=8), + "D/H/chi" : Item(status=' ', wc_rev=8), + "D/H/omega" : Item(status=' ', wc_rev=8), + "D/H/psi" : Item(status=' ', wc_rev=8), + "" : Item(status=' M', wc_rev=8), + }) + k_expected_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : '/A_COPY:2-8'}), + 'B' : Item(), + 'B/lambda' : Item("This is the file 'lambda'.\n"), + 'B/E' : Item(), + 'B/E/alpha' : Item("This is the file 'alpha'.\n"), + 'B/E/beta' : Item("New content"), + 'B/F' : Item(), + 'mu' : Item("Changed on the branch."), + 'C' : Item(), + 'D' : Item(), + 'D/gamma' : Item("This is the file 'gamma'.\n"), + '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/G/tauprime' : Item("This is the file 'tau'.\n"), + 'D/H' : Item(), + 'D/H/chi' : Item("This is the file 'chi'.\n"), + 'D/H/omega' : Item("New content"), + 'D/H/psi' : Item("New content"), + }) + expected_skip = wc.State(A_path, {}) + run_and_verify_reintegrate(A_path, + sbox.repo_url + '/A_COPY', + expected_output, + expected_mergeinfo_output, + expected_elision_output, + k_expected_disk, + k_expected_status, + expected_skip, + [], True, True) + + # Finally, commit the result of the merge (r9). + expected_output = wc.State(wc_dir, { + 'A/D/G/tauprime' : Item(verb='Adding'), + 'A/mu' : Item(verb='Sending'), + 'A' : Item(verb='Sending'), + }) + expected_status.add({ + 'A/D/G/tauprime' : Item(status=' ', wc_rev=9), + }) + expected_status.tweak('A', 'A/mu', wc_rev=9) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + +#---------------------------------------------------------------------- +@SkipUnless(server_has_mergeinfo) +def reintegrate_fail_on_modified_wc(sbox): + "merge --reintegrate should fail in modified wc" + sbox.build() + wc_dir = sbox.wc_dir + A_path = sbox.ospath('A') + A_COPY_path = sbox.ospath('A_COPY') + mu_path = os.path.join(A_path, "mu") + ignored_expected_disk, ignored_expected_status = set_up_branch(sbox) + + # Do a 'sync' merge first so that the following merge really needs to be a + # reintegrate, so that an equivalent automatic merge would behave the same. + svntest.main.run_svn(None, 'merge', sbox.repo_url + '/A', A_COPY_path) + sbox.simple_commit() + + svntest.main.file_write(mu_path, "Changed on 'trunk' (the merge target).") + expected_skip = wc.State(wc_dir, {}) + sbox.simple_update() # avoid mixed-revision error + run_and_verify_reintegrate( + A_path, sbox.repo_url + '/A_COPY', None, None, None, + None, None, expected_skip, + ".*Cannot merge into a working copy that has local modifications.*", + True, False) + +#---------------------------------------------------------------------- +@SkipUnless(server_has_mergeinfo) +def reintegrate_fail_on_mixed_rev_wc(sbox): + "merge --reintegrate should fail in mixed-rev wc" + sbox.build() + wc_dir = sbox.wc_dir + A_path = sbox.ospath('A') + mu_path = os.path.join(A_path, "mu") + ignored_expected_disk, expected_status = set_up_branch(sbox) + # Make and commit a change, in order to get a mixed-rev wc. + svntest.main.file_write(mu_path, "Changed on 'trunk' (the merge target).") + expected_output = wc.State(wc_dir, { + 'A/mu' : Item(verb='Sending'), + }) + expected_status.tweak('A/mu', wc_rev=7) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + expected_skip = wc.State(wc_dir, {}) + # Try merging into that same wc, expecting failure. + run_and_verify_reintegrate( + A_path, sbox.repo_url + '/A_COPY', None, None, None, + None, None, expected_skip, + ".*Cannot merge into mixed-revision working copy.*", + True, False) + +#---------------------------------------------------------------------- +@SkipUnless(server_has_mergeinfo) +def reintegrate_fail_on_switched_wc(sbox): + "merge --reintegrate should fail in switched wc" + sbox.build() + wc_dir = sbox.wc_dir + A_path = sbox.ospath('A') + A_COPY_path = sbox.ospath('A_COPY') + G_path = os.path.join(A_path, "D", "G") + switch_url = sbox.repo_url + "/A/D/H" + expected_disk, expected_status = set_up_branch(sbox) + + # Do a 'sync' merge first so that the following merge really needs to be a + # reintegrate, so that an equivalent automatic merge would behave the same. + expected_disk.tweak( + 'A_COPY/D/H/psi', + 'A_COPY/D/G/rho', + 'A_COPY/B/E/beta', + 'A_COPY/D/H/omega', + contents="New content") + expected_status.tweak( + 'A_COPY/D/H/psi', + 'A_COPY/D/G/rho', + 'A_COPY/B/E/beta', + 'A_COPY/D/H/omega', + 'A_COPY', + wc_rev=7) + svntest.main.run_svn(None, 'merge', sbox.repo_url + '/A', A_COPY_path) + sbox.simple_commit() + + # Switch a subdir of the target. + expected_output = svntest.wc.State(wc_dir, { + 'A/D/G/pi' : Item(status='D '), + 'A/D/G/rho' : Item(status='D '), + 'A/D/G/tau' : Item(status='D '), + 'A/D/G/chi' : Item(status='A '), + 'A/D/G/psi' : Item(status='A '), + 'A/D/G/omega' : Item(status='A '), + }) + expected_disk.remove('A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau') + expected_disk.add({ + 'A/D/G/chi' : Item(contents="This is the file 'chi'.\n"), + 'A/D/G/psi' : Item(contents="New content"), + 'A/D/G/omega' : Item(contents="New content"), + }) + expected_status.remove('A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau') + expected_status.add({ + 'A/D/G' : Item(status=' ', wc_rev=7, switched='S'), + 'A/D/G/chi' : Item(status=' ', wc_rev=7), + 'A/D/G/psi' : Item(status=' ', wc_rev=7), + 'A/D/G/omega' : Item(status=' ', wc_rev=7), + }) + svntest.actions.run_and_verify_switch(wc_dir, + G_path, + switch_url, + expected_output, + expected_disk, + expected_status, + [], + False, '--ignore-ancestry') + sbox.simple_update() # avoid mixed-revision error + expected_skip = wc.State(wc_dir, {}) + run_and_verify_reintegrate( + A_path, sbox.repo_url + '/A_COPY', None, None, None, + None, None, expected_skip, + ".*Cannot merge into a working copy with a switched subtree.*", + True, False) + +#---------------------------------------------------------------------- +# Test for issue #3603 'allow reintegrate merges into WCs with +# missing subtrees'. +@SkipUnless(server_has_mergeinfo) +@Issue(3603) +def reintegrate_on_shallow_wc(sbox): + "merge --reintegrate in shallow wc" + + # Create a standard greek tree, branch A to A_COPY in r2. + sbox.build() + wc_dir = sbox.wc_dir + expected_disk, expected_status = set_up_branch(sbox, branch_only = True) + + # Some paths we'll care about + A_path = sbox.ospath('A') + A_D_path = sbox.ospath('A/D') + mu_COPY_path = sbox.ospath('A_COPY/mu') + psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') + A_COPY_path = sbox.ospath('A_COPY') + + # r3 - Make a change on the A_COPY branch that will be + # reintegrated back to A. + svntest.main.file_write(mu_COPY_path, "branch work") + svntest.main.run_svn(None, 'commit', '-m', + 'Some work on the A_COPY branch', wc_dir) + + # First try a reintegrate where the target WC has a shallow subtree + # that is not affected by the reintegrate. In this case we set the + # depth of A/D to empty. Since the only change made on the branch + # since the branch point is to A_COPY/mu, the reintegrate should + # simply work and update A/mu with the branch's contents. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + svntest.actions.run_and_verify_svn(None, [], 'up', + '--set-depth', 'empty', A_D_path) + expected_output = wc.State(A_path, { + 'mu' : Item(status='U '), + }) + expected_mergeinfo_output = wc.State(A_path, { + '' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_path, { + }) + expected_A_status = wc.State(A_path, { + '' : Item(status=' M'), + 'B' : Item(status=' '), + 'mu' : Item(status='M '), + 'B/E' : Item(status=' '), + 'B/E/alpha' : Item(status=' '), + 'B/E/beta' : Item(status=' '), + 'B/lambda' : Item(status=' '), + 'B/F' : Item(status=' '), + 'C' : Item(status=' '), + 'D' : Item(status=' '), # Don't expect anything under D, + # its depth is empty! + }) + expected_A_status.tweak(wc_rev=3) + expected_A_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : '/A_COPY:2-3'}), + 'B' : Item(), + 'mu' : Item("branch work"), + '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(), # Don't expect anything under D, its depth is empty! + }) + expected_A_skip = wc.State(A_path, {}) + run_and_verify_reintegrate(A_path, + sbox.repo_url + '/A_COPY', + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_A_disk, + expected_A_status, + expected_A_skip, + [], 1, 1) + + # Now revert the reintegrate and make a second change on the + # branch in r4, but this time change a subtree that corresponds + # to the missing (shallow) portion of the source. The reintegrate + # should still succeed. + svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) + svntest.main.file_write(psi_COPY_path, "more branch work") + svntest.main.run_svn(None, 'commit', '-m', + 'Some more work on the A_COPY branch', wc_dir) + # Reuse the same expectations as the prior merge, except for the mergeinfo + # on the target root that now includes the latest rev on the branch. + expected_mergeinfo_output.add({ + 'D' : Item(status=' U') + }) + expected_A_status.tweak('D', status=' M') + expected_A_disk.tweak('D', props={SVN_PROP_MERGEINFO : '/A_COPY/D:2-4*'}) + # ... a depth-restricted item is skipped ... + expected_A_skip.add({ + 'D/H' : Item(verb='Skipped missing target') + }) + expected_output.add({ + # Below the skip + 'D/H/psi' : Item(status=' ', treeconflict='U'), + }) + # Currently this fails due to r1424469. For a full explanation see + # http://svn.haxx.se/dev/archive-2012-12/0472.shtml + # and http://svn.haxx.se/dev/archive-2012-12/0475.shtml + expected_A_disk.tweak('', props={SVN_PROP_MERGEINFO : '/A_COPY:2-4'}) + run_and_verify_reintegrate(A_path, + sbox.repo_url + '/A_COPY', + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_A_disk, + expected_A_status, + expected_A_skip, + [], 1, 1) + +#---------------------------------------------------------------------- +@SkipUnless(server_has_mergeinfo) +def reintegrate_fail_on_stale_source(sbox): + "merge --reintegrate should fail on stale source" + sbox.build() + wc_dir = sbox.wc_dir + expected_disk, expected_status = set_up_branch(sbox) + A_path = sbox.ospath('A') + mu_path = os.path.join(A_path, "mu") + svntest.main.file_append(mu_path, 'some text appended to mu\n') + svntest.actions.run_and_verify_svn(None, [], 'commit', + '-m', 'a change to mu', mu_path) + # Unmix the revisions in the working copy. + svntest.actions.run_and_verify_svn(None, [], 'update', wc_dir) + # The merge --reintegrate succeeds but since there were no changes + # on A_COPY after it was branched the only result is updated mergeinfo + # on the reintegrate target. + expected_output = wc.State(A_path, {}) + expected_mergeinfo_output = wc.State(A_path, { + '' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_path, { + }) + expected_status = wc.State(A_path, { + '' : Item(status=' M'), + 'B' : Item(status=' '), + 'mu' : Item(status=' '), + 'B/E' : Item(status=' '), + 'B/E/alpha' : Item(status=' '), + 'B/E/beta' : Item(status=' '), + 'B/lambda' : Item(status=' '), + 'B/F' : Item(status=' '), + 'C' : Item(status=' '), + 'D' : Item(status=' '), + 'D/G' : Item(status=' '), + 'D/G/pi' : Item(status=' '), + 'D/G/rho' : Item(status=' '), + 'D/G/tau' : Item(status=' '), + 'D/gamma' : Item(status=' '), + 'D/H' : Item(status=' '), + 'D/H/chi' : Item(status=' '), + 'D/H/psi' : Item(status=' '), + 'D/H/omega' : Item(status=' '), + }) + expected_status.tweak(wc_rev=7) + expected_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : '/A_COPY:2-7'}), + 'B' : Item(), + 'mu' : Item("This is the file 'mu'.\nsome text appended to 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("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_skip = wc.State(A_path, { }) + run_and_verify_reintegrate(A_path, + sbox.repo_url + '/A_COPY', + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip, + [], True, True) + +#---------------------------------------------------------------------- +@SkipUnless(server_has_mergeinfo) +def merge_file_with_space_in_its_path(sbox): + "merge a file with space in its path" + + sbox.build() + wc_dir = sbox.wc_dir + some_dir = sbox.ospath('some dir') + file1 = os.path.join(some_dir, "file1") + file2 = os.path.join(some_dir, "file2") + + # Make r2. + os.mkdir(some_dir) + svntest.main.file_append(file1, "Initial text in the file.\n") + svntest.main.run_svn(None, "add", some_dir) + svntest.actions.run_and_verify_svn(None, [], + "ci", "-m", "r2", wc_dir) + + # Make r3. + svntest.main.run_svn(None, "copy", file1, file2) + svntest.actions.run_and_verify_svn(None, [], + "ci", "-m", "r3", wc_dir) + + # Make r4. + svntest.main.file_append(file2, "Next line of text in the file.\n") + svntest.actions.run_and_verify_svn(None, [], + "ci", "-m", "r4", wc_dir) + + target_url = sbox.repo_url + '/some%20dir/file2' + run_reintegrate(target_url, file1) + +#---------------------------------------------------------------------- +@SkipUnless(server_has_mergeinfo) +def reintegrate_with_subtree_mergeinfo(sbox): + "merge --reintegrate with subtree mergeinfo" + + # Create a standard greek tree, branch A to A_COPY in r2, A to A_COPY_2 in + # r3, A to A_COPY_3 in r4, and then make some changes under A in r5-8. + # + # A_COPY_3 4--------- + # / + # A -1--------5-6-7-8- + # \ \ + # A_COPY 2-\----------- + # \ + # A_COPY_2 3--------- + + sbox.build() + wc_dir = sbox.wc_dir + expected_disk, expected_status = set_up_branch(sbox, False, 3) + + # Some paths we'll care about + gamma_COPY_3_path = sbox.ospath('A_COPY_3/D/gamma') + D_path = sbox.ospath('A/D') + gamma_path = sbox.ospath('A/D/gamma') + mu_COPY_2_path = sbox.ospath('A_COPY_2/mu') + mu_path = sbox.ospath('A/mu') + mu_COPY_path = sbox.ospath('A_COPY/mu') + A_COPY_path = sbox.ospath('A_COPY') + D_COPY_path = sbox.ospath('A_COPY') + beta_COPY_path = sbox.ospath('A_COPY/B/E/beta') + gamma_COPY_path = sbox.ospath('A_COPY/D/gamma') + gamma_moved_COPY_path = sbox.ospath('A_COPY/D/gamma_moved') + gamma_moved_path = sbox.ospath('A/D/gamma_moved') + rho_COPY_path = sbox.ospath('A_COPY/D/G/rho') + omega_COPY_path = sbox.ospath('A_COPY/D/H/omega') + psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') + D_COPY_path = sbox.ospath('A_COPY/D') + alpha_COPY_path = sbox.ospath('A_COPY/B/E/alpha') + A_path = sbox.ospath('A') + + # Now set up a situation where we try to reintegrate A_COPY back to A but + # both of these paths have subtree mergeinfo. Iff the mergeinfo on A_COPY + # reflects that the same revisions have been applied across all of A_COPY, + # then the reintegrate merge should succeed. We'll try that case first. + # + # A_COPY_3 4-------[9]-- + # / \ + # / \ + # A -1--------5-6-7-8---10-------------------WC-- + # \ \ (D) \ /reint. + # \ \ (mu) \ / + # A_COPY 2-\--------------------12---13--14------ + # \ / + # \ / + # A_COPY_2 3-------------[11]-- + # + # Key: [#] = cherry-picked revision; (foo) = merge of subtree 'foo' + # Note: These diagrams show an overview and do not capture every detail. + + # r9 - Make a text change to A_COPY_3/D/gamma + svntest.main.file_write(gamma_COPY_3_path, "New content") + expected_output = wc.State(wc_dir, {'A_COPY_3/D/gamma' : Item(verb='Sending')}) + expected_status.tweak('A_COPY_3/D/gamma', wc_rev=9) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + + # r10 - Merge r9 from A_COPY_3/D to A/D, creating explicit subtree + # mergeinfo under A. For this and every subsequent merge we update the WC + # first to allow full inheritance and elision. + svntest.actions.run_and_verify_svn(exp_noop_up_out(9), [], 'up', + wc_dir) + expected_status.tweak(wc_rev=9) + svntest.actions.run_and_verify_svn( + expected_merge_output([[9]], + ['U ' + gamma_path + '\n', + ' U ' + D_path + '\n',]), + [], 'merge', '-c9', sbox.repo_url + '/A_COPY_3/D', D_path) + expected_output = wc.State(wc_dir, + {'A/D' : Item(verb='Sending'), + 'A/D/gamma' : Item(verb='Sending')}) + expected_status.tweak('A/D', 'A/D/gamma', wc_rev=10) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + + # r11 - Make a text change to A_COPY_2/mu + svntest.main.file_write(mu_COPY_2_path, "New content") + expected_output = wc.State(wc_dir, {'A_COPY_2/mu' : Item(verb='Sending')}) + expected_status.tweak('A_COPY_2/mu', wc_rev=11) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + + # r12 - Merge r11 from A_COPY_2/mu to A_COPY/mu + svntest.actions.run_and_verify_svn(exp_noop_up_out(11), [], 'up', + wc_dir) + expected_status.tweak(wc_rev=11) + svntest.actions.run_and_verify_svn( + expected_merge_output([[11]], + ['U ' + mu_COPY_path + '\n', + ' U ' + mu_COPY_path + '\n',]), + [], 'merge', '-c11', sbox.repo_url + '/A_COPY_2/mu', mu_COPY_path) + expected_output = wc.State(wc_dir, + {'A_COPY/mu' : Item(verb='Sending')}) + expected_status.tweak('A_COPY/mu', wc_rev=12) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + + # r13 - Do a 'synch' cherry harvest merge of all available revisions + # from A to A_COPY + svntest.actions.run_and_verify_svn(exp_noop_up_out(12), [], 'up', + wc_dir) + expected_status.tweak(wc_rev=12) + svntest.actions.run_and_verify_svn( + expected_merge_output([[2,12]], + ['U ' + beta_COPY_path + '\n', + 'U ' + gamma_COPY_path + '\n', + 'U ' + rho_COPY_path + '\n', + 'U ' + omega_COPY_path + '\n', + 'U ' + psi_COPY_path + '\n', + ' U ' + A_COPY_path + '\n', + ' U ' + D_COPY_path + '\n', + ' G ' + D_COPY_path + '\n',]), + [], 'merge', sbox.repo_url + '/A', A_COPY_path) + expected_output = wc.State(wc_dir, + {'A_COPY' : Item(verb='Sending'), + #'A_COPY/mu' : Item(verb='Sending'), + 'A_COPY/B/E/beta' : Item(verb='Sending'), + 'A_COPY/D' : Item(verb='Sending'), + 'A_COPY/D/G/rho' : Item(verb='Sending'), + 'A_COPY/D/H/omega' : Item(verb='Sending'), + 'A_COPY/D/H/psi' : Item(verb='Sending'), + 'A_COPY/D/gamma' : Item(verb='Sending')}) + expected_status.tweak('A_COPY', + #'A_COPY/mu', + 'A_COPY/B/E/beta', + 'A_COPY/D', + 'A_COPY/D/G/rho', + 'A_COPY/D/H/omega', + 'A_COPY/D/H/psi', + 'A_COPY/D/gamma', + wc_rev=13) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + + # r14 - Make a text change on A_COPY/B/E/alpha + svntest.main.file_write(alpha_COPY_path, "New content") + expected_output = wc.State(wc_dir, {'A_COPY/B/E/alpha' : Item(verb='Sending')}) + expected_status.tweak('A_COPY/B/E/alpha', wc_rev=14) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + + # Now, reintegrate A_COPY to A. This should succeed. + svntest.actions.run_and_verify_svn(exp_noop_up_out(14), [], 'up', + wc_dir) + expected_status.tweak(wc_rev=14) + expected_output = wc.State(A_path, { + 'B/E/alpha' : Item(status='U '), + 'mu' : Item(status='UU'), + 'D' : Item(status=' U'), + }) + expected_mergeinfo_output = wc.State(A_path, { + '' : Item(status=' U'), + 'mu' : Item(status=' G'), + 'D' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_path, { + }) + expected_A_status = wc.State(A_path, { + '' : Item(status=' M'), + 'B' : Item(status=' '), + 'mu' : Item(status='MM'), + 'B/E' : Item(status=' '), + 'B/E/alpha' : Item(status='M '), + 'B/E/beta' : Item(status=' '), + 'B/lambda' : Item(status=' '), + 'B/F' : Item(status=' '), + 'C' : Item(status=' '), + 'D' : Item(status=' M'), + 'D/G' : Item(status=' '), + 'D/G/pi' : Item(status=' '), + 'D/G/rho' : Item(status=' '), + 'D/G/tau' : Item(status=' '), + 'D/gamma' : Item(status=' '), + 'D/H' : Item(status=' '), + 'D/H/chi' : Item(status=' '), + 'D/H/psi' : Item(status=' '), + 'D/H/omega' : Item(status=' '), + }) + expected_A_status.tweak(wc_rev=14) + expected_A_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : '/A_COPY:2-14'}), + 'B' : Item(), + 'mu' : Item("New content", + props={SVN_PROP_MERGEINFO : + '/A_COPY/mu:2-14\n/A_COPY_2/mu:11'}), + 'B/E' : Item(), + 'B/E/alpha' : Item("New content"), + 'B/E/beta' : Item("New content"), + 'B/lambda' : Item("This is the file 'lambda'.\n"), + 'B/F' : Item(), + 'C' : Item(), + 'D' : Item(props= + {SVN_PROP_MERGEINFO : '/A_COPY/D:2-14\n/A_COPY_3/D:9'}), + '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("New content"), + '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(A_COPY_path, {}) + run_and_verify_reintegrate(A_path, + sbox.repo_url + '/A_COPY', + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_A_disk, + expected_A_status, + expected_A_skip, + [], 1, 1) + + # Make some more changes to A_COPY so that the same revisions have *not* + # been uniformly applied from A to A_COPY. In this case the reintegrate + # merge should fail, but should provide a helpful message as to where the + # problems are. + # + # A_COPY_3 4-------[9]-- + # / \ + # / \ [-8]___ + # A -1---------5-6-7-8---10----------------\-------WC-- + # \ \ (D) \ \ /reint. + # \ \ (mu) \ \ / + # A_COPY 2-\--------------------12---13--14--15-------- + # \ / (D) + # \ / + # A_COPY_2 3-------------[11]-- + + # First revert the previous reintegrate merge + svntest.actions.run_and_verify_svn(None, [], + 'revert', '-R', wc_dir) + + # r15 - Reverse Merge r8 from A/D to A_COPY/D. + svntest.actions.run_and_verify_svn( + expected_merge_output([[-8]], + ['U ' + omega_COPY_path + '\n', + ' U ' + D_COPY_path + '\n',]), + [], 'merge', '-c-8', sbox.repo_url + '/A/D', D_COPY_path) + expected_output = wc.State(wc_dir, + {'A_COPY/D' : Item(verb='Sending'), + 'A_COPY/D/H/omega' : Item(verb='Sending')}) + expected_status.tweak('A_COPY/D', 'A_COPY/D/H/omega', wc_rev=15) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + + # Now reintegrate A_COPY back to A. Since A_COPY/D no longer has r8 merged + # to it from A, the merge should fail. Further we expect an error message + # that highlights the fact that A_COPY/D is the offending subtree. + # + # We want to know that the error provides specific information about the + # paths that are stopping --reintegrate from working. + run_reintegrate_expect_error(sbox.repo_url + '/A_COPY', A_path, + [], + "(svn: E195016: Reintegrate can only be used if " + "revisions 2 through 15 were previously " + "merged from .*/A to the reintegrate source, " + "but this is not the case:\n)" + "|( A_COPY/D\n)" + "|( Missing ranges: /A/D:8\n)" + "|( A_COPY/mu\n)" + "|( Missing ranges: /A/mu:2-12\n)" + "|(\n)") + + # Test another common situation that can break reintegrate as a result + # of copies and moves: + # + # A) On our 'trunk' rename a subtree in such a way as the new + # subtree has explicit mergeinfo. Commit this rename as rev N. + # + # B) Synch merge the rename in A) to our 'branch' in rev N+1. The + # renamed subtree now has the same explicit mergeinfo on both + # the branch and trunk. + # + # C) Make some more changes on the renamed subtree in 'trunk' and + # commit in rev N+2. + # + # D) Synch merge the changes in C) from 'trunk' to 'branch' and commit in + # rev N+3. The renamed subtree on 'branch' now has additional explicit + # mergeinfo describing the synch merge from trunk@N+1 to trunk@N+2. + # + # E) Reintegrate 'branch' to 'trunk'. + # + # Step: A B C D E + # A_COPY_3 ---[9]-- + # / \ (D/g.-> + # / \ [-8]___ D/g.m.) (D/g.m.) + # A ------------10----------------\------16-------18--------WC + # \\ (D) \ \ \ \ /reint. + # \\ (mu) \ \ \ \ / + # A_COPY -\--------------12---13--14--15-------17-------19------ + # \ / (D) + # \ / + # A_COPY_2 --------[11]-- + + # r16 - A) REPOS-to-REPOS rename of A/D/gamma to A/D/gamma_moved. Since + # r874258 WC-to-WC moves won't create mergeinfo on the dest if the source + # doesn't have any. So do a repos-to-repos move so explicit mergeinfo + # *is* created on the destination. + svntest.actions.run_and_verify_svn(None,[], 'move', + sbox.repo_url + '/A/D/gamma', + sbox.repo_url + '/A/D/gamma_moved', + '-m', 'REPOS-to-REPOS move' + ) + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + expected_status.tweak(wc_rev=16) + expected_status.remove('A/D/gamma') + expected_status.add({'A/D/gamma_moved' : Item(status=' ', wc_rev=16)}) + + # Why is gamma_moved notified as ' G' rather than ' U'? It was + # added by the merge and there is only a single editor drive, so + # how can any prop changes be merged to it? The answer is that + # the merge code does some quiet housekeeping, merging gamma_moved's + # inherited mergeinfo into its incoming mergeinfo, see + # http://subversion.tigris.org/issues/show_bug.cgi?id=4309 + # This test is not covering issue #4309 so we let the current + # behavior pass. + # r17 - B) Synch merge from A to A_COPY + svntest.actions.run_and_verify_svn( + expected_merge_output([[8], [13,16], [2,16]], + ['U ' + omega_COPY_path + '\n', + 'A ' + gamma_moved_COPY_path + '\n', + 'D ' + gamma_COPY_path + '\n', + ' U ' + A_COPY_path + '\n', + ' U ' + D_COPY_path + '\n', + ' G ' + gamma_moved_COPY_path + '\n']), + [], 'merge', sbox.repo_url + '/A', A_COPY_path) + expected_output = wc.State( + wc_dir, + {'A_COPY' : Item(verb='Sending'), # Mergeinfo update + 'A_COPY/D' : Item(verb='Sending'), # Mergeinfo update + 'A_COPY/D/gamma' : Item(verb='Deleting'), + 'A_COPY/D/gamma_moved' : Item(verb='Adding'), + 'A_COPY/D/H/omega' : Item(verb='Sending'), # Redoing r15's + # reverse merge of r8. + }) + expected_status.remove('A_COPY/D/gamma') + + expected_status.tweak('A_COPY', + 'A_COPY/D', + 'A_COPY/D/H/omega', + wc_rev=17) + expected_status.add({'A_COPY/D/gamma_moved' : Item(status=' ', wc_rev=17)}) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + + # r18 - C) Text mod to A/D/gamma_moved + svntest.main.file_write(gamma_moved_path, "Even newer content") + expected_output = wc.State(wc_dir, {'A/D/gamma_moved' : Item(verb='Sending')}) + expected_status.tweak('A/D/gamma_moved', wc_rev=18) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + + # r19 - D) Synch merge from A to A_COPY + svntest.actions.run_and_verify_svn( + expected_merge_output([[17,18], [2,18]], + ['U ' + gamma_moved_COPY_path + '\n', + ' U ' + A_COPY_path + '\n', + ' U ' + D_COPY_path + '\n', + ' U ' + gamma_moved_COPY_path + '\n']), + [], 'merge', '--allow-mixed-revisions', sbox.repo_url + '/A', A_COPY_path) + expected_output = wc.State( + wc_dir, + {'A_COPY' : Item(verb='Sending'), # Mergeinfo update + 'A_COPY/D' : Item(verb='Sending'), # Mergeinfo update + 'A_COPY/D/gamma_moved' : Item(verb='Sending'), # Text change + }) + expected_status.tweak('A_COPY', + 'A_COPY/D', + 'A_COPY/D/gamma_moved', + wc_rev=19) + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + + # Reintegrate A_COPY to A, this should work since + # A_COPY/D/gamma_moved's natural history, + # + # /A/D/gamma:1-15 + # /A/D/gamma_moved:16 + # /A_COPY/D/gamma_moved:17-19 + # + # shows that it is fully synched up with trunk. + svntest.actions.run_and_verify_svn(exp_noop_up_out(19), [], 'up', + wc_dir) + expected_output = wc.State(A_path, { + 'B/E/alpha' : Item(status='U '), + 'mu' : Item(status='UU'), + 'D' : Item(status=' U'), + 'D/gamma_moved' : Item(status=' U'), + }) + expected_mergeinfo_output = wc.State(A_path, { + '' : Item(status=' U'), + 'mu' : Item(status=' G'), + 'D' : Item(status=' U'), + 'D/gamma_moved' : Item(status=' G'), # More issue #4309 (see above) + }) + expected_elision_output = wc.State(A_path, { + }) + expected_A_status = wc.State(A_path, { + '' : Item(status=' M'), + 'B' : Item(status=' '), + 'mu' : Item(status='MM'), + 'B/E' : Item(status=' '), + 'B/E/alpha' : Item(status='M '), + 'B/E/beta' : Item(status=' '), + 'B/lambda' : Item(status=' '), + 'B/F' : Item(status=' '), + 'C' : Item(status=' '), + 'D' : Item(status=' M'), + 'D/G' : Item(status=' '), + 'D/G/pi' : Item(status=' '), + 'D/G/rho' : Item(status=' '), + 'D/G/tau' : Item(status=' '), + 'D/gamma_moved' : Item(status=' M'), + 'D/H' : Item(status=' '), + 'D/H/chi' : Item(status=' '), + 'D/H/psi' : Item(status=' '), + 'D/H/omega' : Item(status=' '), + }) + expected_A_status.tweak(wc_rev=19) + expected_A_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : '/A_COPY:2-19'}), + 'B' : Item(), + 'mu' : Item("New content", + props={SVN_PROP_MERGEINFO : + '/A_COPY/mu:2-19\n/A_COPY_2/mu:11'}), + 'B/E' : Item(), + 'B/E/alpha' : Item("New content"), + 'B/E/beta' : Item("New content"), + 'B/lambda' : Item("This is the file 'lambda'.\n"), + 'B/F' : Item(), + 'C' : Item(), + 'D' : Item(props={SVN_PROP_MERGEINFO : + '/A_COPY/D:2-19\n/A_COPY_3/D:9'}), + '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"), + # What's with all this mergeinfo? + # + # '/A/D/gamma_moved:2-7,9-12' - Incoming from the merge source. Yes, + # this mergeinfo describes non-existent path-revs, this is the effect + # of issue #3669 'inheritance can result in mergeinfo describing + # nonexistent sources', but there is already a test for that issue so + # we tolerate it here. + # + # '/A_COPY/D/gamma_moved:17-19' - Describes the merge performed. + # + # '/A_COPY_3/D/gamma:9' - Explicit prior to the merge. + # + #'/A_COPY_3/D/gamma_moved:9' - Incoming from the merge source. + # For the curious, this was originally created in r17 when we merged + # ^/A to A_COPY. This merge added A_COPY/D/gamma_moved, which had + # explicit mergeinfo and due to issue #4309 'wrong notification and + # bogus mergeinfo during merge which adds subtree with mergeinfo' + # this file inherited this bogus mergeinfo from A_COPY/D. Yes, this + # is all quite ugly as the intersection or multiple known issues + # is likely to be. However, given that none of this mergeinfo is + # particularly harmful and that this test is *not* about issues #3669 + # or #4309, we are tolerting it. + 'D/gamma_moved' : Item( + "Even newer content", props={SVN_PROP_MERGEINFO : + '/A/D/gamma_moved:2-7,9-12\n' + '/A_COPY/D/gamma_moved:17-19\n' + '/A_COPY_3/D/gamma:9\n' + '/A_COPY_3/D/gamma_moved:9'}), + '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(A_COPY_path, {}) + run_and_verify_reintegrate(A_path, + sbox.repo_url + '/A_COPY', + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_A_disk, + expected_A_status, + expected_A_skip, + [], 1, 1) + +#---------------------------------------------------------------------- +@SkipUnless(server_has_mergeinfo) +def multiple_reintegrates_from_the_same_branch(sbox): + "multiple reintegrates create self-referential" + + # Make A_COPY branch in r2, and do a few more commits to A in r3-6. + sbox.build() + wc_dir = sbox.wc_dir + expected_disk, expected_status = set_up_branch(sbox) + + # Some paths we'll care about + A_path = sbox.ospath('A') + mu_path = sbox.ospath('A/mu') + A_COPY_path = sbox.ospath('A_COPY') + psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') + Feature_branch_path = sbox.ospath('A_FEATURE_BRANCH') + Feature_beta_path = os.path.join(wc_dir, "A_FEATURE_BRANCH", "B", "E", + "beta") + + # Create a feature branch and do multiple reintegrates from the branch + # without deleting and recreating it. We don't recommend doing this, + # but regardless, it shouldn't create self-referential mergeinfo on + # the reintegrate target. + # + # r7 - Create the feature branch. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + svntest.actions.run_and_verify_svn(None, [], + 'copy', A_path, Feature_branch_path) + svntest.actions.run_and_verify_svn(None, [], + 'ci', '-m', 'Make a feature branch', + wc_dir) + + # r8 - Make a change under 'A'. + svntest.main.file_write(mu_path, "New trunk content.\n") + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + "A text change under 'A'", + wc_dir) + + # r9 - Make a change on the feature branch. + svntest.main.file_write(Feature_beta_path, "New branch content.\n") + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + "A text change on the feature branch", + wc_dir) + + # r10 - Sync merge all changes from 'A' to the feature branch. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + svntest.actions.run_and_verify_svn(None, [], 'merge', + sbox.repo_url + '/A', + Feature_branch_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + "Sync merge 'A' to feature branch", + wc_dir) + + # r11 - Reintegrate the feature branch back to 'A'. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + run_reintegrate(sbox.repo_url + '/A_FEATURE_BRANCH', A_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + "Reintegrate feature branch back to 'A'", + wc_dir) + + # r12 - Do a --record-only merge from 'A' to the feature branch so we + # don't try to merge r11 from trunk during the next sync merge. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + svntest.actions.run_and_verify_svn(None, [], 'merge', '-c11', + '--record-only', + sbox.repo_url + '/A', + Feature_branch_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + "Sync merge 'A' to feature branch", + wc_dir) + + # r13 - Make another change on the feature branch. + svntest.main.file_write(Feature_beta_path, "Even newer branch content.\n") + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + "Different text on the feature branch", + wc_dir) + + # r14 - Sync merge all changes from 'A' to the feature branch in + # preparation for a second reintegrate from this branch. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + svntest.actions.run_and_verify_svn(None, [], 'merge', + sbox.repo_url + '/A', + Feature_branch_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + "2nd Sync merge 'A' to feature branch", + wc_dir) + + # r15 - Reintegrate the feature branch back to 'A' a second time. + # No self-referential mergeinfo should be applied on 'A'. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + expected_output = wc.State(A_path, { + #'' : Item(status=' U'), #<-- no self-referential mergeinfo applied! + 'B/E/beta' : Item(status='U '), + }) + expected_mergeinfo_output = wc.State(A_path, { + '' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_path, { + }) + expected_status = wc.State(A_path, { + '' : Item(status=' M'), + 'B' : Item(status=' '), + 'mu' : Item(status=' '), + 'B/E' : Item(status=' '), + 'B/E/alpha' : Item(status=' '), + 'B/E/beta' : Item(status='M '), + 'B/lambda' : Item(status=' '), + 'B/F' : Item(status=' '), + 'C' : Item(status=' '), + 'D' : Item(status=' '), + 'D/G' : Item(status=' '), + 'D/G/pi' : Item(status=' '), + 'D/G/rho' : Item(status=' '), + 'D/G/tau' : Item(status=' '), + 'D/gamma' : Item(status=' '), + 'D/H' : Item(status=' '), + 'D/H/chi' : Item(status=' '), + 'D/H/psi' : Item(status=' '), + 'D/H/omega' : Item(status=' '), + }) + expected_status.tweak(wc_rev=14) + expected_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : + # Prior to r????? we'd get this + # self-referential mergeinfo: + #'/A:2-6\n/A_FEATURE_BRANCH:7-14'}), + '/A_FEATURE_BRANCH:7-14'}), + 'B' : Item(), + 'mu' : Item("New trunk content.\n"), + 'B/E' : Item(), + 'B/E/alpha' : Item("This is the file 'alpha'.\n"), + 'B/E/beta' : Item("Even newer branch content.\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("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_skip = wc.State(A_path, { }) + run_and_verify_reintegrate(A_path, + sbox.repo_url + '/A_FEATURE_BRANCH', + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip, + [], 1, 1) + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + "2nd Reintegrate feature branch back to 'A'", + wc_dir) + + # Demonstrate the danger of any self-referential mergeinfo on trunk. + # + # Merge all available revisions except r3 from 'A' to 'A_COPY'. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + svntest.actions.run_and_verify_svn(None, [], 'merge', '-r3:HEAD', + sbox.repo_url + '/A', + A_COPY_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + "Merge -r3:HEAD from 'A' to 'A_COPY'", + wc_dir) + # No self-referential mergeinfo should have been carried on 'A_COPY' from + # 'A' that would prevent the following merge from being operative. + svntest.actions.run_and_verify_svn( + expected_merge_output([[2,3],[2,16]], + ['U ' + psi_COPY_path + '\n', + ' U ' + A_COPY_path + '\n',]), + [], 'merge', '--allow-mixed-revisions', sbox.repo_url + '/A', A_COPY_path) + +#---------------------------------------------------------------------- +# Test for a reintegrate bug which can occur when the merge source +# has mergeinfo that explicitly describes common history with the reintegrate +# target, see http://svn.haxx.se/dev/archive-2009-12/0338.shtml +# +# Also tests Issue #3591 'reintegrate merges update subtree mergeinfo +# unconditionally'. +@SkipUnless(server_has_mergeinfo) +@Issue(3591) +def reintegrate_with_self_referential_mergeinfo(sbox): + "source has target's history as explicit mergeinfo" + + sbox.build() + wc_dir = sbox.wc_dir + + # Make some changes under 'A' in r2-5. + wc_disk, wc_status = set_up_branch(sbox, nbr_of_branches=0) + + # Some paths we'll care about + A_path = sbox.ospath('A') + A2_path = sbox.ospath('A2') + A2_B_path = sbox.ospath('A2/B') + A2_1_path = sbox.ospath('A2.1') + A2_1_mu_path = sbox.ospath('A2.1/mu') + + # r6 Copy A to A2 and then manually set some self-referential mergeinfo on + # A2/B and A2. + svntest.actions.run_and_verify_svn(exp_noop_up_out(5), [], + 'up', wc_dir) + svntest.actions.run_and_verify_svn(None, [], + 'copy', A_path, A2_path) + # /A:3 describes A2's natural history, a.k.a. it's implicit mergeinfo, so + # it is self-referential. Same for /A/B:4 and A2/B. Normally this is + # redundant but not harmful. + svntest.actions.run_and_verify_svn(None, [], + 'ps', 'svn:mergeinfo', '/A:3', A2_path) + svntest.actions.run_and_verify_svn(None, [], + 'ps', 'svn:mergeinfo', '/A/B:4', A2_B_path) + svntest.actions.run_and_verify_svn( + None, [], 'ci', '-m', + 'copy A to A2 and set some self-referential mergeinfo on the latter.', + wc_dir) + + # r7 Copy A2 to A2.1 + svntest.actions.run_and_verify_svn(None, [], + 'copy', A2_path, A2_1_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', + '-m', 'copy A2to A2.1.', wc_dir) + + # r8 Make a change on A2.1/mu + svntest.main.file_write(A2_1_mu_path, 'New A2.1 stuff') + svntest.actions.run_and_verify_svn(None, [], 'ci', + '-m', 'Work done on the A2.1 branch.', + wc_dir) + + # Update to uniform revision and reintegrate A2.1 back to A2. + # Note that the mergeinfo on A2/B is not changed by the reintegration + # and so is not expected to by updated to describe the merge. + svntest.actions.run_and_verify_svn(exp_noop_up_out(8), [], + 'up', wc_dir) + expected_output = wc.State(A2_path, { + 'mu' : Item(status='U '), + }) + expected_mergeinfo_output = wc.State(A2_path, { + '' : Item(status=' U'), + }) + expected_elision_output = wc.State(A2_path, { + }) + expected_status = wc.State(A2_path, { + '' : Item(status=' M'), + 'B' : Item(status=' '), + 'mu' : Item(status='M '), + 'B/E' : Item(status=' '), + 'B/E/alpha' : Item(status=' '), + 'B/E/beta' : Item(status=' '), + 'B/lambda' : Item(status=' '), + 'B/F' : Item(status=' '), + 'C' : Item(status=' '), + 'D' : Item(status=' '), + 'D/G' : Item(status=' '), + 'D/G/pi' : Item(status=' '), + 'D/G/rho' : Item(status=' '), + 'D/G/tau' : Item(status=' '), + 'D/gamma' : Item(status=' '), + 'D/H' : Item(status=' '), + 'D/H/chi' : Item(status=' '), + 'D/H/psi' : Item(status=' '), + 'D/H/omega' : Item(status=' '), + }) + expected_status.tweak(wc_rev=8) + expected_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : '/A:3\n/A2.1:7-8'}), + 'B' : Item(props={SVN_PROP_MERGEINFO : '/A/B:4'}), + 'mu' : Item("New A2.1 stuff"), + '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_skip = wc.State(A2_path, { }) + # Previously failed with this error: + # + # svn merge ^/A2.1" A2 --reintegrate + # ..\..\..\subversion\svn\merge-cmd.c:349: (apr_err=160013) + # ..\..\..\subversion\libsvn_client\merge.c:9219: (apr_err=160013) + # ..\..\..\subversion\libsvn_client\ra.c:728: (apr_err=160013) + # ..\..\..\subversion\libsvn_client\mergeinfo.c:733: (apr_err=160013) + # ..\..\..\subversion\libsvn_client\ra.c:526: (apr_err=160013) + # ..\..\..\subversion\libsvn_repos\rev_hunt.c:908: (apr_err=160013) + # ..\..\..\subversion\libsvn_repos\rev_hunt.c:607: (apr_err=160013) + # ..\..\..\subversion\libsvn_fs_fs\tree.c:2886: (apr_err=160013) + # ..\..\..\subversion\libsvn_fs_fs\tree.c:669: (apr_err=160013) + # svn: File not found: revision 4, path '/A2' + run_and_verify_reintegrate(A2_path, + sbox.repo_url + '/A2.1', + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip, + [], 1, 0) + +#---------------------------------------------------------------------- +# Test for issue #3577 '1.7 subtree mergeinfo recording breaks reintegrate' +# and issue #4329 'automatic merge uses reintegrate type merge if source is +# fully synced'. +@Issue(3577,4329) +@SkipUnless(server_has_mergeinfo) +def reintegrate_with_subtree_merges(sbox): + "reintegrate with prior subtree merges to source" + + # Create a standard greek tree, branch A to A_COPY in r2, and make + # some changes under A in r3-6. + sbox.build() + wc_dir = sbox.wc_dir + expected_disk, expected_status = set_up_branch(sbox) + + # Some paths we'll care about + A_path = sbox.ospath('A') + psi_path = sbox.ospath('A/D/H/psi') + mu_COPY_path = sbox.ospath('A_COPY/mu') + A_COPY_path = sbox.ospath('A_COPY') + B_COPY_path = sbox.ospath('A_COPY/B') + rho_COPY_path = sbox.ospath('A_COPY/D/G/rho') + H_COPY_path = sbox.ospath('A_COPY/D/H') + + # r7 - Make a change on the A_COPY branch that will be + # reintegrated back to A. + svntest.main.file_write(mu_COPY_path, "branch work") + svntest.main.run_svn(None, 'commit', '-m', + 'Some work on the A_COPY branch', wc_dir) + + # Update the WC to a uniform revision, then merge all of the changes + # from A to A_COPY, but do it via subtree merges so the mergeinfo + # record of the merges insn't neatly reflected in the root of the + # branch. Commit the merge as r8. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + svntest.actions.run_and_verify_svn(None, [], 'merge', '-c5', + sbox.repo_url + '/A/B', + B_COPY_path) + svntest.actions.run_and_verify_svn(None, [], 'merge', '-c4', + sbox.repo_url + '/A/D/G/rho', + rho_COPY_path) + svntest.actions.run_and_verify_svn(None, [], 'merge', '-c3', + sbox.repo_url + '/A/D/H', + H_COPY_path) + svntest.actions.run_and_verify_svn(None, [], 'merge', '-c6', + sbox.repo_url + '/A', + A_COPY_path) + svntest.actions.run_and_verify_svn(None, [], 'commit', '-m', + 'Merge everything from A to A_COPY', + wc_dir) + + # Now update the WC and try to reintegrate. Since we really have merged + # everything from A to A_COPY, even though it was done via subtree merges, + # the reintegrate should succeed. Previously it failed because the naive + # interpretation of the mergeinfo on A_COPY didn't reflect that it was + # fully synced with A, resulting in this error: + # + # svn merge ^/A_COPY A --reintegrate + # ..\..\..\subversion\svn\merge-cmd.c:358: (apr_err=195016) + # ..\..\..\subversion\libsvn_client\merge.c:9318: (apr_err=195016) + # svn: Reintegrate can only be used if revisions 2 through 7 were + # previously merged from file:///C%3A/SVN/src-trunk-2/Debug/subversion + # /tests/cmdline/svn-test-work/repositories/merge_tests-142/A to the + # reintegrate source, but this is not the case: + # A_COPY + # Missing ranges: /A:2-5 + # A_COPY/B + # Missing ranges: /A/B:2-4,6 + # A_COPY/D/G/rho + # Missing ranges: /A/D/G/rho:2-3,5-6 + # A_COPY/D/H + # Missing ranges: /A/D/H:2,4-5 + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + expected_output = wc.State(A_path, { + 'mu' : Item(status='U '), + }) + expected_mergeinfo_output = wc.State(A_path, { + '' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_path, { + }) + expected_A_status = wc.State(A_path, { + '' : Item(status=' M'), + 'B' : Item(status=' '), + 'mu' : Item(status='M '), + 'B/E' : Item(status=' '), + 'B/E/alpha' : Item(status=' '), + 'B/E/beta' : Item(status=' '), + 'B/lambda' : Item(status=' '), + 'B/F' : Item(status=' '), + 'C' : Item(status=' '), + 'D' : Item(status=' '), + 'D/G' : Item(status=' '), + 'D/G/pi' : Item(status=' '), + 'D/G/rho' : Item(status=' '), + 'D/G/tau' : Item(status=' '), + 'D/gamma' : Item(status=' '), + 'D/H' : Item(status=' '), + 'D/H/chi' : Item(status=' '), + 'D/H/psi' : Item(status=' '), + 'D/H/omega' : Item(status=' '), + }) + expected_A_status.tweak(wc_rev=8) + expected_A_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : '/A_COPY:2-8'}), + 'B' : Item(), + 'mu' : Item("branch work"), + '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(A_COPY_path, {}) + run_and_verify_reintegrate(A_path, + sbox.repo_url + '/A_COPY', + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_A_disk, + expected_A_status, + expected_A_skip, + [], 1, 1) + + # Test issue #4329. Revert previous merge and commit a new edit to + # A/D/H/psi. Attempt the same merge without the --reintegrate option. + # It should succeed because the automatic merge code should detect that + # a reintegrate-style merge is required, that merge should succeed and + # there should be not conflict on A/D/H/psi. + svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) + svntest.main.file_write(psi_path, "Non-conflicting trunk edit.\n") + svntest.main.run_svn(None, 'commit', '-m', + 'An edit on trunk prior to reintegrate.', wc_dir) + sbox.simple_update() + expected_A_status.tweak(wc_rev=9) + expected_A_disk.tweak('', props={SVN_PROP_MERGEINFO: '/A_COPY:2-9'}) + expected_A_disk.tweak('D/H/psi', contents='Non-conflicting trunk edit.\n') + svntest.actions.run_and_verify_merge(A_path, None, None, + sbox.repo_url + '/A_COPY', None, + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_A_disk, expected_A_status, + expected_A_skip, + [], True, False, A_path) + +#---------------------------------------------------------------------- +# Test for issue #3654 'added subtrees with mergeinfo break reintegrate'. +@SkipUnless(server_has_mergeinfo) +@Issue(3654) +def added_subtrees_with_mergeinfo_break_reintegrate(sbox): + "added subtrees with mergeinfo break reintegrate" + + sbox.build() + wc_dir = sbox.wc_dir + + # Some paths we'll care about + A_path = sbox.ospath('A') + nu_path = sbox.ospath('A/C/nu') + mu_path = sbox.ospath('A/mu') + A_COPY_path = sbox.ospath('A_COPY') + lambda_COPY_path = sbox.ospath('A_COPY/B/lambda') + A_COPY_2_path = sbox.ospath('A_COPY_2') + nu_COPY_2_path = sbox.ospath('A_COPY_2/C/nu') + + # Branch A@1 to A_COPY and A_COPY_2 in r2 and r3 respectively. + # Make some changes under 'A' in r4-7. + wc_disk, wc_status = set_up_branch(sbox, nbr_of_branches=2) + + # r8 - Add a new file A_COPY_2/C/nu. + svntest.main.file_write(nu_COPY_2_path, "This is the file 'nu'.\n") + svntest.actions.run_and_verify_svn(None, [], 'add', nu_COPY_2_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', + '-m', 'Add new file in A_COPY_2 branch', + wc_dir) + + + # r9 - Cyclic cherry pick merge r8 from A_COPY_2 back to A. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], + 'merge', '-c', '8', + sbox.repo_url + '/A_COPY_2', + A_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', + '-m', 'Merge r8 from A_COPY_2 to A.', + wc_dir) + + # r10 - Make an edit to A_COPY_2/C/nu. + svntest.main.file_write(nu_COPY_2_path, "A_COPY_2 edit to file 'nu'.\n") + svntest.actions.run_and_verify_svn(None, [], 'ci', + '-m', 'Edit new file on A_COPY_2 branch', + wc_dir) + + # r11 - Cyclic subtree cherry pick merge r10 from A_COPY_2/C/nu + # back to A/C/nu. + svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], + 'merge', '-c', '10', + sbox.repo_url + '/A_COPY_2/C/nu', + nu_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + 'Merge r8 from A_COPY_2/C/nu to A/C/nu.', + wc_dir) + + # r12 - Edit under A_COPY. + svntest.main.file_write(mu_path, "mu edits on A_COPY.\n") + svntest.actions.run_and_verify_svn(None, [], 'ci', + '-m', 'Work on A_COPY branch.', + wc_dir) + + # r13 - Sync merge A to A_COPY in preparation for reintegrate. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], + 'merge', sbox.repo_url + '/A', A_COPY_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + 'Prep for reintegrate: Sync A to A_COPY.', + wc_dir) + + # r14 - Reintegrate A_COPY to A. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + run_reintegrate(sbox.repo_url + '/A_COPY', A_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + 'Reintegrate A_COPY to A.', + wc_dir) + + # r15 - Delete A_COPY. + svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], + 'delete', A_COPY_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + 'Delete A_COPY branch', wc_dir) + + # r16 - Create new A_COPY from A@HEAD=15. + # + # Update so we copy HEAD: + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], + 'copy', A_path, A_COPY_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + 'Create new A_COPY branch from A', wc_dir) + + # r17 - Unrelated edits under both A and A_COPY. + svntest.main.file_write(nu_path, "Trunk work on nu.\n") + svntest.main.file_write(lambda_COPY_path, "lambda edit on A_COPY.\n") + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + 'Unrelated edits on A and A_COPY branch.', + wc_dir) + + # r18 - Sync A to A_COPY in preparation for another reintegrate. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], + 'merge', sbox.repo_url + '/A', A_COPY_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', + 'Prep for reintegrate: Sync A to A_COPY.', + wc_dir) + + # Reintegrate A_COPY back to A. We just synced A_COPY with A, so this + # should work. The only text change should be the change made to + # A_COPY/B/lambda in r17 after the new A_COPY was created. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + expected_output = wc.State(A_path, { + '' : Item(status=' U'), + 'B/lambda' : Item(status='U '), + 'C/nu' : Item(status=' U'), + }) + expected_mergeinfo_output = wc.State(A_path, { + '' : Item(status=' U'), + 'C/nu' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_path, { + }) + expected_status = wc.State(A_path, { + '' : Item(status=' M'), + 'B' : Item(status=' '), + 'mu' : Item(status=' '), + 'B/E' : Item(status=' '), + 'B/E/alpha' : Item(status=' '), + 'B/E/beta' : Item(status=' '), + 'B/lambda' : Item(status='M '), + 'B/F' : Item(status=' '), + 'C' : Item(status=' '), + 'C/nu' : Item(status=' M'), + 'D' : Item(status=' '), + 'D/G' : Item(status=' '), + 'D/G/pi' : Item(status=' '), + 'D/G/rho' : Item(status=' '), + 'D/G/tau' : Item(status=' '), + 'D/gamma' : Item(status=' '), + 'D/H' : Item(status=' '), + 'D/H/chi' : Item(status=' '), + 'D/H/psi' : Item(status=' '), + 'D/H/omega' : Item(status=' '), + }) + expected_status.tweak(wc_rev=18) + expected_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : + '/A_COPY:2-13,16-18\n' + # ^ ^ + # | | + # from _| | + # 1st | + # reintegrate | + # | + # from this reintegrate + # + '/A_COPY_2:8'}), # <-- From cyclic merge in r9 + 'B' : Item(), + 'mu' : Item("mu edits on A_COPY.\n"), # From earlier reintegrate. + 'B/E' : Item(), + 'B/E/alpha' : Item("This is the file 'alpha'.\n"), + 'B/E/beta' : Item("New content"), + 'B/lambda' : Item("lambda edit on A_COPY.\n"), # From this reintegrate. + 'B/F' : Item(), + 'C' : Item(), + 'C/nu' : Item("Trunk work on nu.\n", + props={SVN_PROP_MERGEINFO : + '/A_COPY/C/nu:13,16-18\n' + '/A_COPY_2/C/nu:10'}), # <-- From cyclic + # merge in r11 + '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_skip = wc.State(A_COPY_path, {}) + run_and_verify_reintegrate(A_path, + sbox.repo_url + '/A_COPY', + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip, + [], 1, 1) + +#---------------------------------------------------------------------- +# Test for issue #3648 '2-URL merges incorrectly reverse-merge mergeinfo +# for merge target'. +@SkipUnless(server_has_mergeinfo) +@Issue(3648) +def two_URL_merge_removes_valid_mergeinfo_from_target(sbox): + "2-URL merge removes valid mergeinfo from target" + + sbox.build() + wc_dir = sbox.wc_dir + + # Some paths we'll care about + lambda_COPY_path = sbox.ospath('A_COPY/B/lambda') + mu_path = sbox.ospath('A/mu') + A_COPY_path = sbox.ospath('A_COPY') + A_COPY_2_path = sbox.ospath('A_COPY_2') + + # Branch A@1 to A_COPY r2 + # Branch A@1 to A_COPY_2 in r3. + # Make some changes under 'A' in r4-7. + wc_disk, wc_status = set_up_branch(sbox, nbr_of_branches=2) + + # r8 - A simple text edit on the A_COPY branch. + svntest.main.file_write(lambda_COPY_path, "Edit on 'branch 1'.\n") + svntest.actions.run_and_verify_svn(None, [], 'ci', + '-m', "Work on 'branch 1'.", + wc_dir) + + # r9 - Sync the A_COPY branch with A up the HEAD (r8). Now A_COPY + # differs from A only by the change made in r8 and by the mergeinfo + # '/A:2-8' on A_COPY which was set to describe the merge. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], + 'merge', sbox.repo_url + '/A', A_COPY_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', + '-m', 'Sync A to A_COPY.', + wc_dir) + + # r10 - A simple text edit on our "trunk" A. + svntest.main.file_write(mu_path, "Edit on 'trunk'.\n") + svntest.actions.run_and_verify_svn(None, [], 'ci', + '-m', "Work on 'trunk'", + wc_dir) + + # r11 - Sync the A_COPY_2 branch with A up to HEAD (r10). + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], + 'merge', sbox.repo_url + '/A', + A_COPY_2_path) + svntest.actions.run_and_verify_svn(None, [], 'ci', + '-m', 'Sync A to A_COPY_2.', + wc_dir) + + # Confirm that the mergeinfo on each branch is what we expect. + svntest.actions.run_and_verify_svn([A_COPY_path + ' - /A:2-8\n'], + [], 'pg', SVN_PROP_MERGEINFO, + '-R', A_COPY_path) + svntest.actions.run_and_verify_svn([A_COPY_2_path + ' - /A:3-10\n'], + [], 'pg', SVN_PROP_MERGEINFO, + '-R', A_COPY_2_path) + + # Now say we want to apply the changes made on the first branch (A_COPY) + # to the second branch (A_COPY_2). One way to do this is a 2-URL merge + # between A at the revision last synced to A_COPY and A_COPY_2 at HEAD (r11), + # i.e.: + # + # svn merge ^/A@8 ^/A_COPY@11 A_COPY_2_WC + # + # Recall from the note on r9 that this diff is simply the one text change + # made on branch 1 and some mergeinfo: + # + # >svn diff ^/A@8 ^/A_COPY@11 + # Index: B/lambda + # =================================================================== + # --- B/lambda (.../A) (revision 8) + # +++ B/lambda (.../A_COPY) (revision 11) + # @@ -1 +1 @@ + # -This is the file 'lambda'. + # +Edit on 'branch 1'. + # + # Property changes on: . + # ___________________________________________________________________ + # Added: svn:mergeinfo + # Merged /A:r2-8 + # + # The mergeinfo diff is already represented in A_COPY_2's mergeinfo, so the + # result of the merge should be the text change to lambda and the addition + # of mergeinfo showing that the history of A_COPY is now part of A_COPY_2, + # i.e. '/A_COPY:2-11' + # + # Before issue #3648 was fixed this test failed because the valid mergeinfo + # '/A:r3-8' on A_COPY_2 was removed by the merge. + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + expected_output = wc.State(A_COPY_2_path, { + '' : Item(status=' G'), + 'B/lambda' : Item(status='U '), + }) + expected_mergeinfo_output = wc.State(A_COPY_2_path, { + '' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_COPY_2_path, { + }) + expected_status = wc.State(A_COPY_2_path, { + '' : Item(status=' M'), + 'B' : Item(status=' '), + 'mu' : Item(status=' '), + 'B/E' : Item(status=' '), + 'B/E/alpha' : Item(status=' '), + 'B/E/beta' : Item(status=' '), + 'B/lambda' : Item(status='M '), + 'B/F' : Item(status=' '), + 'C' : Item(status=' '), + 'D' : Item(status=' '), + 'D/G' : Item(status=' '), + 'D/G/pi' : Item(status=' '), + 'D/G/rho' : Item(status=' '), + 'D/G/tau' : Item(status=' '), + 'D/gamma' : Item(status=' '), + 'D/H' : Item(status=' '), + 'D/H/chi' : Item(status=' '), + 'D/H/psi' : Item(status=' '), + 'D/H/omega' : Item(status=' '), + }) + expected_status.tweak(wc_rev=11) + expected_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : + '/A:3-10\n/A_COPY:2-11'}), + 'B' : Item(), + 'mu' : Item("Edit on 'trunk'.\n"), + 'B/E' : Item(), + 'B/E/alpha' : Item("This is the file 'alpha'.\n"), + 'B/E/beta' : Item("New content"), + 'B/lambda' : Item("Edit on 'branch 1'.\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_skip = wc.State(A_COPY_path, {}) + svntest.actions.run_and_verify_merge(A_COPY_2_path, 8, 11, + sbox.repo_url + '/A', + sbox.repo_url + '/A_COPY', + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip, + [], True, True) + +#---------------------------------------------------------------------- +# Test for issue #3867 'reintegrate merges create mergeinfo for +# non-existent paths'. +@SkipUnless(server_has_mergeinfo) +@Issue(3867) +def reintegrate_creates_bogus_mergeinfo(sbox): + "reintegrate creates bogus mergeinfo" + + sbox.build() + wc_dir=sbox.wc_dir + + mu_path = sbox.ospath('A/mu') + lambda_path = sbox.ospath('A/B/lambda') + alpha_path = sbox.ospath('A/B/E/alpha') + beta_path = sbox.ospath('A/B/E/beta') + A_path = sbox.ospath('A') + A_path_1 = sbox.ospath('A@1') + A_COPY_path = sbox.ospath('A_COPY') + A_COPY_psi_path = sbox.ospath('A_COPY/D/H/psi') + A_COPY_url = sbox.repo_url + "/A_COPY" + + # Make 2 commits under /A pushing the repo to rev3 + + svntest.main.file_write(mu_path, "New content.\n") + svntest.main.run_svn(None, "ci", "-m", "simple text edit", wc_dir) + svntest.main.file_write(lambda_path, "New content.\n") + svntest.main.run_svn(None, "ci", "-m", "simple text edit", wc_dir) + + # Branch A@1 as A_COPY in revision 4 + + svntest.main.run_svn(None, "cp", A_path_1, A_COPY_path) + svntest.main.run_svn(None, "ci", "-m", "create a branch", wc_dir) + + # Make a text edit on the branch pushing the repo to r5 + svntest.main.file_write(A_COPY_psi_path, "Branch edit.\n") + svntest.main.run_svn(None, "ci", "-m", "branch edit", wc_dir) + + # Sync the A_COPY with A in preparation for reintegrate and commit as r6. + svntest.main.run_svn(None, "up", wc_dir) + svntest.main.run_svn(None, "merge", sbox.repo_url + "/A", A_COPY_path) + svntest.main.run_svn(None, "ci", "-m", "sync A_COPY with A", wc_dir) + + # Update the working copy to allow the merge + svntest.main.run_svn(None, "up", wc_dir) + + # Reintegrate A_COPY to A. The resulting merginfo on A should be + # /A_COPY:4-6 + expected_output = wc.State(A_path, { + 'D/H/psi' : Item(status='U '), + }) + expected_mergeinfo_output = wc.State(A_path, { + '' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_path, { + }) + expected_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : + '/A_COPY:4-6'}), + 'B' : Item(), + 'mu' : Item("New content.\n"), + '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("New content.\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("Branch edit.\n"), + 'D/H/omega' : Item("This is the file 'omega'.\n"), + }) + expected_skip = wc.State(A_COPY_path, {}) + + run_and_verify_reintegrate(A_path, + A_COPY_url, + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, None, expected_skip, + [], + 1, 1) + + +#---------------------------------------------------------------------- +# Test for regression on 1.6.x branch, merge fails when source without +# subtree mergeinfo is reintegrated into a target with subtree +# mergeinfo. Deliberately written in a style that works with the 1.6 +# testsuite. +@SkipUnless(server_has_mergeinfo) +@Issue(3957) +def no_source_subtree_mergeinfo(sbox): + "source without subtree mergeinfo" + + sbox.build() + wc_dir=sbox.wc_dir + + svntest.main.file_write(sbox.ospath('A/B/E/alpha'), + 'AAA\n' + + 'X\n' + + 'BBB\n' + + 'Y\n' + + 'CCC\n') + sbox.simple_commit() + sbox.simple_update() + + # Create branch-1 + svntest.main.run_svn(None, 'copy', + sbox.ospath('A/B'), + sbox.ospath('A/B1')) + sbox.simple_commit() + + # Create branch-1 + svntest.main.run_svn(None, 'copy', + sbox.ospath('A/B'), + sbox.ospath('A/B2')) + sbox.simple_commit() + + # Change on trunk + svntest.main.file_write(sbox.ospath('A/B/E/alpha'), + 'AAAxx\n' + + 'X\n' + + 'BBB\n' + + 'Y\n' + + 'CCC\n') + sbox.simple_commit() + + # Change on branch-1 + svntest.main.file_write(sbox.ospath('A/B1/E/alpha'), + 'AAA\n' + + 'X\n' + + 'BBBxx\n' + + 'Y\n' + + 'CCC\n') + sbox.simple_commit() + + # Change on branch-2 + svntest.main.file_write(sbox.ospath('A/B2/E/alpha'), + 'AAA\n' + + 'X\n' + + 'BBB\n' + + 'Y\n' + + 'CCCxx\n') + sbox.simple_commit() + sbox.simple_update() + + # Merge trunk to branch-1 + # svntest.main.run_svn(None, 'merge', '^/A/B', sbox.ospath('A/B1')) + A_B1 = sbox.ospath('A/B1') + expected_output = wc.State(A_B1, { + 'E/alpha' : Item(status='U '), + }) + expected_skip = wc.State(A_B1, { }) + svntest.actions.run_and_verify_merge(A_B1, None, None, '^/A/B', None, + expected_output, None, None, None, None, + expected_skip, []) + sbox.simple_commit() + sbox.simple_update() + + # Reintegrate branch-1 subtree to trunk subtree + run_reintegrate('^/A/B1/E', sbox.ospath('A/B/E')) + sbox.simple_commit() + sbox.simple_update() + + # Merge trunk to branch-2 + #svntest.main.run_svn(None, 'merge', '^/A/B', sbox.ospath('A/B2')) + A_B2 = sbox.ospath('A/B2') + expected_output = wc.State(A_B2, { + 'E' : Item(status=' U'), + 'E/alpha' : Item(status='U '), + }) + expected_skip = wc.State(A_B1, { }) + svntest.actions.run_and_verify_merge(A_B2, None, None, '^/A/B', None, + expected_output, None, None, None, None, + expected_skip, []) + sbox.simple_commit() + svntest.main.run_svn(None, 'update', wc_dir) + + # Reverse merge branch-1 subtree to branch-2 subtree, this removes + # the subtree mergeinfo from branch 2 + #svntest.main.run_svn(None, 'merge', '-r8:2', + # '^/A/B1/E', sbox.ospath('A/B2/E')) + A_B2_E = sbox.ospath('A/B2/E') + expected_output = wc.State(A_B2_E, { + 'alpha' : Item(status='U '), + }) + expected_skip = wc.State(A_B2_E, { }) + svntest.actions.run_and_verify_merge(A_B2_E, 8, 2, '^/A/B1/E', None, + expected_output, None, None, None, None, + expected_skip, []) + sbox.simple_commit() + svntest.main.run_svn(None, 'update', wc_dir) + + # Verify that merge results in no subtree mergeinfo + expected_stderr = '.*W200017: Property.*not found' + svntest.actions.run_and_verify_svn([], expected_stderr, + 'propget', 'svn:mergeinfo', + sbox.repo_url + '/A/B2/E') + + # Merge trunk to branch-2 + svntest.main.run_svn(None, 'merge', '^/A/B', sbox.ospath('A/B2')) + sbox.simple_commit() + sbox.simple_update() + + # Verify that there is still no subtree mergeinfo + svntest.actions.run_and_verify_svn([], expected_stderr, + 'propget', 'svn:mergeinfo', + sbox.repo_url + '/A/B2/E') + + # Reintegrate branch-2 to trunk, this fails in 1.6.x from 1.6.13. + # The error message states revisions /A/B/E:3-11 are missing from + # /A/B2/E and yet the mergeinfo on /A/B2 is /A/B:3-11 and /A/B2/E + # has no mergeinfo. + expected_output = wc.State(sbox.ospath('A/B'), { + 'E' : Item(status=' U'), + 'E/alpha' : Item(status='U '), + }) + expected_mergeinfo = wc.State(sbox.ospath('A/B'), { + '' : Item(status=' U'), + }) + expected_elision = wc.State(sbox.ospath('A/B'), { + }) + expected_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : '/A/B2:4-12'}), + 'E' : Item(), + 'E/alpha' : Item("AAA\n" + + "X\n" + + "BBB\n" + + "Y\n" + + "CCCxx\n"), + 'E/beta' : Item("This is the file 'beta'.\n"), + 'F' : Item(), + 'lambda' : Item("This is the file 'lambda'.\n"), + }) + expected_skip = wc.State(sbox.ospath('A/B'), { + }) + run_and_verify_reintegrate(sbox.ospath('A/B'), + '^/A/B2', + expected_output, expected_mergeinfo, + expected_elision, expected_disk, + None, expected_skip, + [], + 1, 1) + +#---------------------------------------------------------------------- +@SkipUnless(server_has_mergeinfo) +@Issue(3961) +def reintegrate_replaced_source(sbox): + "reintegrate a replaced source branch" + + # Make A_COPY branch in r2, and do a few more commits to A in r3-6. + sbox.build() + wc_dir = sbox.wc_dir + expected_disk, expected_status = set_up_branch(sbox) + + A_path = sbox.ospath('A') + A_COPY_path = sbox.ospath('A_COPY') + beta_COPY_path = sbox.ospath('A_COPY/B/E/beta') + mu_COPY_path = sbox.ospath('A_COPY/mu') + + # Using cherrypick merges, simulate a series of sync merges from A to + # A_COPY with a replace of A_COPY along the way. + # + # r7 - Merge r3 from A to A_COPY + svntest.main.run_svn(None, 'up', wc_dir) + svntest.main.run_svn(None, 'merge', sbox.repo_url + '/A', A_COPY_path, + '-c3') + sbox.simple_commit(message='Merge r3 from A to A_COPY') + + # r8 - Merge r4 from A to A_COPY + svntest.main.run_svn(None, 'up', wc_dir) + svntest.main.run_svn(None, 'merge', sbox.repo_url + '/A', A_COPY_path, + '-c4') + sbox.simple_commit(message='Merge r4 from A to A_COPY') + + # r9 - Merge r5 from A to A_COPY. Make an additional edit to + # A_COPY/B/E/beta. + svntest.main.run_svn(None, 'up', wc_dir) + svntest.main.run_svn(None, 'merge', sbox.repo_url + '/A', A_COPY_path, + '-c5') + svntest.main.file_write(beta_COPY_path, "Branch edit mistake.\n") + sbox.simple_commit(message='Merge r5 from A to A_COPY') + + # r10 - Delete A_COPY and replace it with A_COPY@8. This removes the edit + # we made above in r9 to A_COPY/B/E/beta. + svntest.main.run_svn(None, 'up', wc_dir) + svntest.main.run_svn(None, 'delete', A_COPY_path) + svntest.main.run_svn(None, 'copy', sbox.repo_url + '/A_COPY@8', + A_COPY_path) + sbox.simple_commit(message='Replace A_COPY with A_COPY@8') + + # r11 - Make an edit on A_COPY/mu. + svntest.main.file_write(mu_COPY_path, "Branch edit.\n") + sbox.simple_commit(message='Branch edit') + + # r12 - Do a final sync merge of A to A_COPY in preparation for + # reintegration. + svntest.main.run_svn(None, 'up', wc_dir) + svntest.main.run_svn(None, 'merge', sbox.repo_url + '/A', A_COPY_path) + sbox.simple_commit(message='Sync A_COPY with A') + + # Reintegrate A_COPY to A. The resulting mergeinfo should be + # '/A_COPY:2-8,10-12' because of the replacement which removed /A_COPY:9 + # from the reintegrate source's history. + svntest.main.run_svn(None, 'up', wc_dir) + expected_output = wc.State(A_path, { + 'mu' : Item(status='U '), + }) + expected_mergeinfo_output = wc.State(A_path, { + '' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_path, { + }) + expected_status = wc.State(A_path, { + '' : Item(status=' M'), + 'B' : Item(status=' '), + 'mu' : Item(status='M '), + 'B/E' : Item(status=' '), + 'B/E/alpha' : Item(status=' '), + 'B/E/beta' : Item(status=' '), + 'B/lambda' : Item(status=' '), + 'B/F' : Item(status=' '), + 'C' : Item(status=' '), + 'D' : Item(status=' '), + 'D/G' : Item(status=' '), + 'D/G/pi' : Item(status=' '), + 'D/G/rho' : Item(status=' '), + 'D/G/tau' : Item(status=' '), + 'D/gamma' : Item(status=' '), + 'D/H' : Item(status=' '), + 'D/H/chi' : Item(status=' '), + 'D/H/psi' : Item(status=' '), + 'D/H/omega' : Item(status=' '), + }) + expected_status.tweak(wc_rev=12) + expected_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : '/A_COPY:2-8,10-12'}), + 'B' : Item(), + 'mu' : Item("Branch edit.\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("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_skip = wc.State(A_path, { }) + run_and_verify_reintegrate(A_path, + sbox.repo_url + '/A_COPY', + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip, + [], True, True) + +#---------------------------------------------------------------------- +@SkipUnless(svntest.main.is_posix_os) +@SkipUnless(server_has_mergeinfo) +@Issue(4052) +def reintegrate_symlink_deletion(sbox): + "reintegrate symlink deletion" + + sbox.build() + wc_dir = sbox.wc_dir + + ## path vars + A_path = sbox.ospath('A') + A_omicron_path = sbox.ospath('A/omicron') + mu_path = sbox.ospath('A/mu') + A_COPY_path = sbox.ospath('A_COPY') + A_COPY_omicron_path = sbox.ospath('A_COPY/omicron') + A_url = sbox.repo_url + "/A" + A_COPY_url = sbox.repo_url + "/A_COPY" + + ## add symlink + os.symlink(mu_path, A_omicron_path) + sbox.simple_add('A/omicron') + sbox.simple_commit(message='add symlink') + + ## branch + sbox.simple_repo_copy('A', 'A_COPY') + sbox.simple_update() + + ## branch rm + sbox.simple_rm('A_COPY/omicron') + sbox.simple_commit(message='remove symlink on branch') + + ## Note: running update at this point avoids the bug. + + ## reintegrate + # ### TODO: verify something here + run_reintegrate(A_COPY_url, A_path) + +#---------------------------------------------------------------------- +@SkipUnless(server_has_mergeinfo) +def no_op_reintegrate(sbox): + """no-op reintegrate""" + + # Make A_COPY branch in r2, and do a few more commits to A in r3-6. + sbox.build() + wc_dir = sbox.wc_dir + A_path = sbox.ospath('A') + A_COPY_path = sbox.ospath('A_COPY') + expected_disk, expected_status = set_up_branch(sbox) + + # Sync merge from trunk to branch + svntest.main.run_svn(None, 'merge', sbox.repo_url + '/A', A_COPY_path) + sbox.simple_commit() + sbox.simple_update() + + # Reintegrate; there are no relevant changes on the branch. + # ### TODO: Check the result more carefully than merely that it completed. + run_reintegrate(sbox.repo_url + '/A_COPY', A_path) + +#---------------------------------------------------------------------- +@SkipUnless(server_has_mergeinfo) +def renamed_branch_reintegrate(sbox): + """reintegrate a branch that has been renamed""" + + # The idea of this test is to ensure that the reintegrate merge is able to + # cope when one or both of the branches have been renamed. + # + # A -1-----3-4-5-6----------------------9-------- + # \ \ / reintegrate + # A_COPY 2--------------7-------- / + # sync \ / + # RENAMED rename 8---------------- + + # TODO: Make some changes between the sync/rename/reintegrate steps so + # the reintegrate merge actually has to do something. + # TODO: Rename the other branch as well. + + # Make A_COPY branch in r2, and do a few more commits to A in r3-6. + sbox.build() + + wc_dir = sbox.wc_dir + A_path = sbox.ospath('A') + A_COPY_path = sbox.ospath('A_COPY') + expected_disk, expected_status = set_up_branch(sbox) + + # Sync merge from trunk to branch + svntest.main.run_svn(None, 'merge', sbox.repo_url + '/A', A_COPY_path) + sbox.simple_commit() + sbox.simple_update() + + # Rename the branch + sbox.simple_move('A_COPY', 'RENAMED') + sbox.simple_commit() + sbox.simple_update() + + # Reintegrate; there are no relevant changes on the branch. + # ### TODO: Check the result more carefully than merely that it completed. + run_reintegrate(sbox.repo_url + '/RENAMED@8', A_path) + +@SkipUnless(server_has_mergeinfo) +def reintegrate_noop_branch_into_renamed_branch(sbox): + """reintegrate no-op branch into renamed branch""" + # In this test, the branch has no unique changes but contains a + # revision cherry-picked from trunk. Reintegrating such a branch + # should work, but used to error out when this test was written. + + # Make A_COPY branch in r2, and do a few more commits to A in r3-6. + sbox.build() + + wc_dir = sbox.wc_dir + A_path = sbox.ospath('A') + A_COPY_path = sbox.ospath('A_COPY') + expected_disk, expected_status = set_up_branch(sbox) + + # Cherry-pick merge from trunk to branch + youngest_rev = sbox.youngest() + svntest.main.run_svn(None, 'merge', '-c', youngest_rev, + sbox.repo_url + '/A', A_COPY_path) + sbox.simple_commit() + sbox.simple_update() + + # Rename the trunk + sbox.simple_move('A', 'A_RENAMED') + sbox.simple_commit() + sbox.simple_update() + + # Try to reintegrate the branch. This should work but used to fail with: + # svn: E160013: File not found: revision 5, path '/A_RENAMED' + run_reintegrate(sbox.repo_url + '/A_COPY', sbox.ospath('A_RENAMED')) + + +######################################################################## +# Run the tests + + +# list all tests here, starting with None: +test_list = [ None, + basic_reintegrate, + reintegrate_with_rename, + reintegrate_branch_never_merged_to, + reintegrate_fail_on_modified_wc, + reintegrate_fail_on_mixed_rev_wc, + reintegrate_fail_on_switched_wc, + reintegrate_on_shallow_wc, + reintegrate_fail_on_stale_source, + merge_file_with_space_in_its_path, + reintegrate_with_subtree_mergeinfo, + multiple_reintegrates_from_the_same_branch, + reintegrate_with_self_referential_mergeinfo, + reintegrate_with_subtree_merges, + added_subtrees_with_mergeinfo_break_reintegrate, + two_URL_merge_removes_valid_mergeinfo_from_target, + reintegrate_creates_bogus_mergeinfo, + no_source_subtree_mergeinfo, + reintegrate_replaced_source, + reintegrate_symlink_deletion, + no_op_reintegrate, + renamed_branch_reintegrate, + reintegrate_noop_branch_into_renamed_branch, + ] + +if __name__ == '__main__': + svntest.main.run_tests(test_list) + # NOTREACHED + + +### End of file. |