Return-Path: X-Original-To: apmail-subversion-commits-archive@minotaur.apache.org Delivered-To: apmail-subversion-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 6339D995C for ; Fri, 13 Jan 2012 21:42:45 +0000 (UTC) Received: (qmail 60091 invoked by uid 500); 13 Jan 2012 21:42:45 -0000 Delivered-To: apmail-subversion-commits-archive@subversion.apache.org Received: (qmail 60072 invoked by uid 500); 13 Jan 2012 21:42:45 -0000 Mailing-List: contact commits-help@subversion.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@subversion.apache.org Delivered-To: mailing list commits@subversion.apache.org Received: (qmail 60065 invoked by uid 99); 13 Jan 2012 21:42:45 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 13 Jan 2012 21:42:45 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 13 Jan 2012 21:42:23 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 921D32388C5A for ; Fri, 13 Jan 2012 21:41:02 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1231318 [30/35] - in /subversion/branches/revprop-packing: ./ build/ build/ac-macros/ build/generator/ build/generator/templates/ build/win32/ contrib/client-side/emacs/ contrib/server-side/mod_dontdothat/ notes/ notes/http-and-webdav/ not... Date: Fri, 13 Jan 2012 21:40:38 -0000 To: commits@subversion.apache.org From: hwright@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20120113214102.921D32388C5A@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Modified: subversion/branches/revprop-packing/subversion/tests/cmdline/log_tests.py URL: http://svn.apache.org/viewvc/subversion/branches/revprop-packing/subversion/tests/cmdline/log_tests.py?rev=1231318&r1=1231317&r2=1231318&view=diff ============================================================================== --- subversion/branches/revprop-packing/subversion/tests/cmdline/log_tests.py (original) +++ subversion/branches/revprop-packing/subversion/tests/cmdline/log_tests.py Fri Jan 13 21:40:26 2012 @@ -34,6 +34,7 @@ from svntest import wc from svntest.main import server_has_mergeinfo from svntest.main import SVN_PROP_MERGEINFO from merge_tests import set_up_branch +from diff_tests import make_diff_header, make_no_diff_deleted_header # (abbreviation) Skip = svntest.testcase.Skip_deco @@ -178,7 +179,8 @@ def guarantee_repos_and_wc(sbox): to test the code""" svntest.main.file_write(msg_file, msg) svntest.main.file_append(iota_path, "8") - svntest.main.file_append(rho_path, "8") + svntest.main.file_append(rho_path, "88") # More than one char so libmagic + # treats it as text. svntest.main.run_svn(None, 'add', rho_path) svntest.main.run_svn(None, 'ci', '-F', msg_file) @@ -424,7 +426,7 @@ class SVNLogParseError(Exception): pass -def parse_log_output(log_lines): +def parse_log_output(log_lines, with_diffs=False): """Return a log chain derived from LOG_LINES. A log chain is a list of hashes; each hash represents one log message, in the order it appears in LOG_LINES (the first log @@ -438,6 +440,7 @@ def parse_log_output(log_lines): 'date' ===> string 'msg' ===> string (the log message itself) 'lines' ===> number (so that it may be checked against rev) + If LOG_LINES contains changed-path information, then the hash also contains @@ -451,7 +454,11 @@ def parse_log_output(log_lines): 'reverse_merges' ===> list of reverse-merging revisions that resulted in this log being part of the list of messages. - """ + + If LOG_LINES contains diffs and WITH_DIFFS=True, then the hash also contains + + 'diff_lines' ===> list of strings (diffs) + """ # Here's some log output to look at while writing this function: @@ -566,6 +573,20 @@ def parse_log_output(log_lines): for line in log_lines[0:lines]: msg += line del log_lines[0:lines] + + # Maybe accumulate a diff. + # If there is a diff, there is a blank line before and after it. + if with_diffs and len(log_lines) >= 2 and log_lines[0] == '\n': + log_lines.pop(0) + diff_lines = [] + while len(log_lines) and log_lines[0] != msg_separator: + diff_lines.append(log_lines.pop(0)) + if diff_lines[-1] == '\n': + diff_lines.pop() + else: + raise SVNLogParseError("no blank line after diff in log") + this_item['diff_lines'] = diff_lines + elif this_line == msg_separator: if this_item: this_item['msg'] = msg @@ -676,6 +697,42 @@ def check_log_chain(chain, revlist, path missing_revs, chain) +def parse_diff(output): + """Return a set containing the various diff bits, broken up by file.""" + + diff_set = [] + current_diff = [] + for line in output: + if line.startswith('Index: ') and current_diff: + diff_set.append(current_diff) + current_diff = [] + current_diff.append(line) + diff_set.append(current_diff) + + return diff_set + + +def setify(diff_list): + """Take a list of lists and make it a set of tuples.""" + s = set() + for diff in diff_list: + s.add(tuple(diff)) + return s + + +def compare_diff_output(expected_diffs, output): + """Compare the diffs in EXPECTED_DIFFS (which is a Python set) with the + text in OUTPUT, remembering that there is no canonical ordering for diffs.""" + + diffs = parse_diff(output) + diffs = setify(diffs) + expected_diffs = setify(expected_diffs) + + if diffs.issubset(expected_diffs) and diffs.issuperset(expected_diffs): + return + + raise svntest.Failure("Diffs not equal") + ###################################################################### # Tests @@ -2028,6 +2085,124 @@ def log_on_nonexistent_path_and_valid_re svntest.actions.run_and_verify_svn(None, None, expected_error, 'log', '-q', bad_path_default_rev) +#---------------------------------------------------------------------- +# Test for issue #4022 'svn log -g interprets change in inherited mergeinfo +# due to move as a merge'. +@Issue(4022) +def merge_sensitive_log_copied_path_inherited_mergeinfo(sbox): + "log -g on copied path with inherited mergeinfo" + + sbox.build() + wc_dir = sbox.wc_dir + wc_disk, wc_status = set_up_branch(sbox, branch_only=True) + + A_path = os.path.join(wc_dir, 'A') + gamma_COPY_path = os.path.join(wc_dir, 'A_COPY', 'D', 'gamma') + old_gamma_path = os.path.join(wc_dir, 'A', 'D', 'gamma') + new_gamma_path = os.path.join(wc_dir, 'A', 'C', 'gamma') + + # r3 - Modify a file (A_COPY/D/gamma) on the branch + svntest.main.file_write(gamma_COPY_path, "Branch edit.\n") + svntest.main.run_svn(None, 'ci', '-m', 'Branch edit', wc_dir) + + # r4 - Reintegrate A_COPY to A + svntest.main.run_svn(None, 'up', wc_dir) + svntest.main.run_svn(None, 'merge', '--reintegrate', + sbox.repo_url + '/A_COPY', A_path) + svntest.main.run_svn(None, 'ci', '-m', 'Reintegrate A_COPY to A', wc_dir) + + # r5 - Move file modified by reintegrate (A/D/gamma to A/C/gamma). + svntest.main.run_svn(None, 'move', old_gamma_path, new_gamma_path) + svntest.main.run_svn(None, 'ci', '-m', 'Move file', wc_dir) + + # 'svn log -g --stop-on-copy ^/A/C/gamma' hould return *only* r5 + # Previously this test failed because the change in gamma's inherited + # mergeinfo between r4 and r5, due to the move, was understood as a merge: + # + # >svn log -v -g --stop-on-copy ^^/A/C/gamma + # ------------------------------------------------------------------------ + # r5 | jrandom | 2011-10-11 14:37:57 -0700 (Tue, 11 Oct 2011) | 1 line # + # Changed paths: + # A /A/C/gamma (from /A/D/gamma:4) + # D /A/D/gamma + # + # Move file + # ------------------------------------------------------------------------ + # r3 | jrandom | 2011-10-11 14:37:56 -0700 (Tue, 11 Oct 2011) | 1 line + # Changed paths: + # M /A_COPY/D/gamma + # Reverse merged via: r5 + # + # Branch edit + # ------------------------------------------------------------------------ + # r2 | jrandom | 2011-10-11 14:37:56 -0700 (Tue, 11 Oct 2011) | 1 line + # Changed paths: + # A /A_COPY (from /A:1) + # Reverse merged via: r5 + # + # log msg + # ------------------------------------------------------------------------ + expected_merges = {5 : []} + svntest.main.run_svn(None, 'up', wc_dir) + exit_code, out, err = svntest.actions.run_and_verify_svn( + None, None, [], 'log', '-g', '--stop-on-copy', + sbox.repo_url + '/A/C/gamma') + log_chain = parse_log_output(out) + check_merge_results(log_chain, expected_merges) + +#---------------------------------------------------------------------- +def log_diff(sbox): + "'svn log --diff'" + + guarantee_repos_and_wc(sbox) + + was_cwd = os.getcwd() + os.chdir(sbox.wc_dir) + + exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [], + 'log', '--diff') + os.chdir(was_cwd) + + for line in output: + if line.startswith('Index:'): + break + else: + raise SVNLogParseError("no diffs found in log output") + + # After a copy, a log of the copy destination used to fail because the + # diff tried to use the head-revision URL with the old revision numbers + # without using the correct peg revision. + + sbox.simple_copy('A', 'A2') + sbox.simple_commit() + + os.chdir(sbox.wc_dir) + exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [], + 'log', '--diff', + '-r10:8', 'A2') + os.chdir(was_cwd) + + r9diff = [ make_no_diff_deleted_header('A2/B/E/alpha', 8, 9), + make_diff_header('A2/B/E/beta', 'revision 8', 'revision 9') + + [ "@@ -1 +1,2 @@\n", + " This is the file 'beta'.\n", + "+9\n", + "\ No newline at end of file\n", + ] + ] + r8diff = [ make_diff_header('A2/D/G/rho', 'revision 0', 'revision 8') + + [ "@@ -0,0 +1 @@\n", + "+88\n", + "\ No newline at end of file\n", + ] + ] + log_chain = parse_log_output(output, with_diffs=True) + if len(log_chain) != 3: + raise SVNLogParseError("%d logs found, 3 expected" % len(log_chain)) + compare_diff_output(r9diff, log_chain[1]['diff_lines']) + compare_diff_output(r8diff, log_chain[2]['diff_lines']) + + ######################################################################## # Run the tests @@ -2067,6 +2242,8 @@ test_list = [ None, merge_sensitive_log_ignores_cyclic_merges, log_with_unrelated_peg_and_operative_revs, log_on_nonexistent_path_and_valid_rev, + merge_sensitive_log_copied_path_inherited_mergeinfo, + log_diff, ] if __name__ == '__main__': Modified: subversion/branches/revprop-packing/subversion/tests/cmdline/merge_authz_tests.py URL: http://svn.apache.org/viewvc/subversion/branches/revprop-packing/subversion/tests/cmdline/merge_authz_tests.py?rev=1231318&r1=1231317&r2=1231318&view=diff ============================================================================== --- subversion/branches/revprop-packing/subversion/tests/cmdline/merge_authz_tests.py (original) +++ subversion/branches/revprop-packing/subversion/tests/cmdline/merge_authz_tests.py Fri Jan 13 21:40:26 2012 @@ -73,7 +73,10 @@ from svntest.actions import inject_confl # This is *not* a full test of issue #2829, see also merge_tests.py, # search for "2829". This tests the problem where a merge adds a path # with a missing sibling and so needs its own explicit mergeinfo. -@Issues(2893,2997,2829) +# +# #4056 - Don't record non-inheritable mergeinfo if missing subtrees are not +# touched by the full-depth diff +@Issues(2893,2997,2829,4056) @SkipUnless(svntest.main.server_has_mergeinfo) @Skip(svntest.main.is_ra_type_file) def mergeinfo_and_skipped_paths(sbox): @@ -393,10 +396,54 @@ def mergeinfo_and_skipped_paths(sbox): # Merge -r7:9 to the restricted WC's A_COPY_2/D/H. # + # r9 adds a path, 'A_COPY_2/D/H/zeta', which has a missing sibling 'psi', + # but since 'psi' is untouched by the merge it isn't skipped, and since it + # isn't skipped, its parent 'A_COPY_2/D/H' won't get non-inheritable + # mergeinfo set on it to describe the merge, so none of the parent's + # children will get explicit mergeinfo -- see issue #4056. + expected_output = wc.State(A_COPY_2_H_path, { + 'omega' : Item(status='U '), + 'zeta' : Item(status='A '), + }) + expected_mergeinfo_output = wc.State(A_COPY_2_H_path, { + '' : Item(status=' U'), + 'omega' : Item(status=' U'), + }) + expected_elision_output = wc.State(A_COPY_2_H_path, { + 'omega' : Item(status=' U'), + }) + expected_status = wc.State(A_COPY_2_H_path, { + '' : Item(status=' M', wc_rev=8), + 'chi' : Item(status=' ', wc_rev=8), + 'omega' : Item(status='M ', wc_rev=8), + 'zeta' : Item(status='A ', copied='+', wc_rev='-'), + }) + expected_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:8-9'}), + 'omega' : Item("New content"), + 'chi' : Item("This is the file 'chi'.\n"), + 'zeta' : Item("This is the file 'zeta'.\n"), + }) + expected_skip = wc.State(A_COPY_2_H_path, {}) + svntest.actions.run_and_verify_merge(A_COPY_2_H_path, '7', '9', + sbox.repo_url + '/A/D/H', None, + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip, + None, None, None, None, + None, 1, 0) + + # Merge -r4:9 to the restricted WC's A_COPY_2/D/H. + # # r9 adds a path, 'A_COPY_2/D/H/zeta', which has a parent with - # non-inheritable mergeinfo (due to the fact 'A_COPY_2/D/H/psi' is missing). - # 'A_COPY_2/D/H/zeta' must therefore get its own explicit mergeinfo from - # this merge. + # non-inheritable mergeinfo (due to the fact 'A_COPY_2/D/H/psi' is missing + # and skipped). 'A_COPY_2/D/H/zeta' must therefore get its own explicit + # mergeinfo from this merge. + svntest.actions.run_and_verify_svn(None, None, [], 'revert', '--recursive', + wc_restricted) expected_output = wc.State(A_COPY_2_H_path, { 'omega' : Item(status='U '), 'zeta' : Item(status='A '), @@ -415,15 +462,17 @@ def mergeinfo_and_skipped_paths(sbox): 'zeta' : Item(status='A ', copied='+', wc_rev='-'), }) expected_disk = wc.State('', { - '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:8-9*'}), + '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5-9*'}), 'omega' : Item("New content", - props={SVN_PROP_MERGEINFO : '/A/D/H/omega:8-9'}), + props={SVN_PROP_MERGEINFO : '/A/D/H/omega:5-9'}), 'chi' : Item("This is the file 'chi'.\n"), 'zeta' : Item("This is the file 'zeta'.\n", props={SVN_PROP_MERGEINFO : '/A/D/H/zeta:9'}), }) - expected_skip = wc.State(A_COPY_2_H_path, {}) - svntest.actions.run_and_verify_merge(A_COPY_2_H_path, '7', '9', + expected_skip = wc.State(A_COPY_2_H_path, { + 'psi' : Item(), + }) + svntest.actions.run_and_verify_merge(A_COPY_2_H_path, '4', '9', sbox.repo_url + '/A/D/H', None, expected_output, expected_mergeinfo_output, Modified: subversion/branches/revprop-packing/subversion/tests/cmdline/merge_reintegrate_tests.py URL: http://svn.apache.org/viewvc/subversion/branches/revprop-packing/subversion/tests/cmdline/merge_reintegrate_tests.py?rev=1231318&r1=1231317&r2=1231318&view=diff ============================================================================== --- subversion/branches/revprop-packing/subversion/tests/cmdline/merge_reintegrate_tests.py (original) +++ subversion/branches/revprop-packing/subversion/tests/cmdline/merge_reintegrate_tests.py Fri Jan 13 21:40:26 2012 @@ -2435,7 +2435,7 @@ def reintegrate_replaced_source(sbox): # Using cherrypick merges, simulate a series of sync merges from A to # A_COPY with a replace of A_COPY along the way. # - # r6 - Merge r3 from A to A_COPY + # 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') Modified: subversion/branches/revprop-packing/subversion/tests/cmdline/merge_tests.py URL: http://svn.apache.org/viewvc/subversion/branches/revprop-packing/subversion/tests/cmdline/merge_tests.py?rev=1231318&r1=1231317&r2=1231318&view=diff ============================================================================== --- subversion/branches/revprop-packing/subversion/tests/cmdline/merge_tests.py (original) +++ subversion/branches/revprop-packing/subversion/tests/cmdline/merge_tests.py Fri Jan 13 21:40:26 2012 @@ -49,15 +49,31 @@ from svntest.actions import make_conflic from svntest.actions import inject_conflict_into_expected_state def expected_merge_output(rev_ranges, additional_lines=None, foreign=False, - elides=False, two_url=False): + elides=False, two_url=False, target=None, + text_conflicts=0, prop_conflicts=0, tree_conflicts=0): """Generate an (inefficient) regex representing the expected merge - output and mergeinfo notifications from REV_RANGES (a list of 'range' lists - of the form [start, end] or [single_rev] --> [single_rev - 1, single_rev]), - and ADDITIONAL_LINES (a list of strings). If REV_RANGES is None then only - the standard notification for a 3-way merge is expected. If ELIDES is true - add to the regex an expression representing elision notification. If TWO_URL - us true tweak the regex to expect the appropriate mergeinfo notification - for a 3-way merge.""" + output and mergeinfo notifications from REV_RANGES and ADDITIONAL_LINES. + + REV_RANGES is a list of revision ranges for which mergeinfo is being + recorded. Each range is of the form [start, end] (where both START and + END are inclusive, unlike in '-rX:Y') or the form [single_rev] (which is + like '-c SINGLE_REV'). If REV_RANGES is None then only the standard + notification for a 3-way merge is expected. + + ADDITIONAL_LINES is a list of strings to match the other lines of output; + these are basically regular expressions except that backslashes will be + escaped herein. + + If ELIDES is true, add to the regex an expression representing elision + notification. If TWO_URL is true, tweak the regex to expect the + appropriate mergeinfo notification for a 3-way merge. + + TARGET is the local path to the target, as it should appear in + notifications; if None, it is not checked. + + TEXT_CONFLICTS, PROP_CONFLICTS and TREE_CONFLICTS specify the number of + each kind of conflict to expect.""" + if rev_ranges is None: lines = [svntest.main.merge_notify_line(None, None, False, foreign)] else: @@ -69,8 +85,8 @@ def expected_merge_output(rev_ranges, ad else: end_rev = None lines += [svntest.main.merge_notify_line(start_rev, end_rev, - True, foreign)] - lines += [svntest.main.mergeinfo_notify_line(start_rev, end_rev)] + True, foreign, target)] + lines += [svntest.main.mergeinfo_notify_line(start_rev, end_rev, target)] if (elides): lines += ["--- Eliding mergeinfo from .*\n"] @@ -94,6 +110,16 @@ def expected_merge_output(rev_ranges, ad if sys.platform == 'win32' and additional_lines != None: additional_lines = additional_lines.replace("\\", "\\\\") lines.append(str(additional_lines)) + + if text_conflicts or prop_conflicts or tree_conflicts: + lines.append("Summary of conflicts:\n") + if text_conflicts: + lines.append(" Text conflicts: %d\n" % text_conflicts) + if prop_conflicts: + lines.append(" Property conflicts: %d\n" % prop_conflicts) + if tree_conflicts: + lines.append(" Tree conflicts: %d\n" % tree_conflicts) + return "|".join(lines) def check_mergeinfo_recursively(root_path, subpaths_mergeinfo): @@ -835,7 +861,8 @@ def merge_similar_unrelated_trees(sbox): #---------------------------------------------------------------------- def merge_one_file_helper(sbox, arg_flav, record_only = 0): - "ARG_FLAV is one of 'r' (revision range) or 'c' (single change)." + """ARG_FLAV is one of 'r' (revision range) or 'c' (single change) or + '*' (no revision specified).""" if arg_flav not in ('r', 'c', '*'): raise svntest.Failure("Unrecognized flavor of merge argument") @@ -972,9 +999,13 @@ def merge_record_only(sbox): merge_one_file_helper(sbox, 'r', 1) #---------------------------------------------------------------------- -# This is a regression for the enhancement added in issue #785. +# This is a regression test for the enhancement added in issue #785 "add +# friendly enhancement to 'svn merge'", which is about inferring that +# the default target of "svn merge [-r...] FILE" should not be "." but +# rather should be "FILE". def merge_with_implicit_target_helper(sbox, arg_flav): - "ARG_FLAV is one of 'r' (revision range) or 'c' (single change)." + """ARG_FLAV is one of 'r' (revision range) or 'c' (single change) or + '*' (no revision specified).""" if arg_flav not in ('r', 'c', '*'): raise svntest.Failure("Unrecognized flavor of merge argument") @@ -2439,7 +2470,7 @@ def set_up_dir_replace(sbox): # A merge that replaces a directory # Tests for Issue #2144 and Issue #2607 @SkipUnless(server_has_mergeinfo) -@Issue(2144) +@Issue(2144,2607) def merge_dir_replace(sbox): "merge a replacement of a directory" @@ -5573,7 +5604,7 @@ def merge_to_switched_path(sbox): # 3188: Mergeinfo on switched targets/subtrees should # elide to repos @SkipUnless(server_has_mergeinfo) -@Issue(2823,2839,3187,3188) +@Issue(2823,2839,3187,3188,4056) def merge_to_path_with_switched_children(sbox): "merge to path with switched children" @@ -5668,18 +5699,18 @@ def merge_to_path_with_switched_children 'omega' : Item(status=' U') }) expected_elision_output = wc.State(A_COPY_H_path, { + 'omega' : Item(status=' U') }) expected_status = wc.State(A_COPY_H_path, { '' : Item(status=' M', wc_rev=8), 'psi' : Item(status=' ', wc_rev=8, switched='S'), - 'omega' : Item(status='MM', wc_rev=8), + 'omega' : Item(status='M ', wc_rev=8), 'chi' : Item(status=' ', wc_rev=8), }) expected_disk = wc.State('', { - '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:8*'}), + '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:8'}), 'psi' : Item("This is the file 'psi'.\n"), - 'omega' : Item("New content", - props={SVN_PROP_MERGEINFO : '/A/D/H/omega:8'}), + 'omega' : Item("New content"), 'chi' : Item("This is the file 'chi'.\n"), }) expected_skip = wc.State(A_COPY_H_path, { }) @@ -5713,7 +5744,7 @@ def merge_to_path_with_switched_children '' : Item(status=' M', wc_rev=8), 'H' : Item(status=' M', wc_rev=8), 'H/chi' : Item(status=' ', wc_rev=8), - 'H/omega' : Item(status='MM', wc_rev=8), + 'H/omega' : Item(status='M ', wc_rev=8), 'H/psi' : Item(status=' ', wc_rev=8, switched='S'), 'G' : Item(status=' M', wc_rev=8, switched='S'), 'G/pi' : Item(status=' ', wc_rev=8), @@ -5723,10 +5754,9 @@ def merge_to_path_with_switched_children }) expected_disk_D = wc.State('', { '' : Item(props={SVN_PROP_MERGEINFO : '/A/D:6*'}), - 'H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:8*'}), + 'H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:8'}), 'H/chi' : Item("This is the file 'chi'.\n"), - 'H/omega' : Item("New content", - props={SVN_PROP_MERGEINFO : '/A/D/H/omega:8'}), + 'H/omega' : Item("New content"), 'H/psi' : Item("This is the file 'psi'.\n",), 'G' : Item(props={SVN_PROP_MERGEINFO : '/A/D/G:6*'}), 'G/pi' : Item("This is the file 'pi'.\n"), @@ -5760,10 +5790,10 @@ def merge_to_path_with_switched_children }) expected_elision_output = wc.State(A_COPY_D_path, { }) - expected_disk_D.tweak('', props={SVN_PROP_MERGEINFO : '/A/D:5-6*'}) - expected_disk_D.tweak('H', props={SVN_PROP_MERGEINFO : '/A/D/H:5*,8*'}) + expected_disk_D.tweak('', props={SVN_PROP_MERGEINFO : '/A/D:5,6*'}) + expected_disk_D.tweak('H', props={SVN_PROP_MERGEINFO : '/A/D/H:5*,8'}) expected_disk_D.tweak('H/psi', contents="New content", - props={SVN_PROP_MERGEINFO :'/A/D/H/psi:5'}) + props={SVN_PROP_MERGEINFO :'/A/D/H/psi:5,8'}) expected_status_D.tweak('H/psi', status='MM') svntest.actions.run_and_verify_merge(A_COPY_D_path, '4', '5', sbox.repo_url + '/A/D', None, @@ -5804,7 +5834,7 @@ def merge_to_path_with_switched_children 'D/H' : Item(status=' M', wc_rev=8), 'D/H/chi' : Item(status=' ', wc_rev=8), 'D/H/psi' : Item(status='MM', wc_rev=8, switched='S'), - 'D/H/omega' : Item(status='MM', wc_rev=8), + 'D/H/omega' : Item(status='M ', wc_rev=8), }) expected_disk = wc.State('', { '' : Item(props={SVN_PROP_MERGEINFO : '/A:5-8'}), @@ -5816,19 +5846,18 @@ def merge_to_path_with_switched_children 'B/lambda' : Item("This is the file 'lambda'.\n"), 'B/F' : Item(), 'C' : Item(), - 'D' : Item(props={SVN_PROP_MERGEINFO : '/A/D:5-6*'}), + 'D' : Item(props={SVN_PROP_MERGEINFO : '/A/D:5,6*'}), 'D/G' : Item(props={SVN_PROP_MERGEINFO : '/A/D/G:6*'}), 'D/G/pi' : Item("This is the file 'pi'.\n"), 'D/G/rho' : Item("New content", props={SVN_PROP_MERGEINFO : '/A/D/G/rho:6'}), 'D/G/tau' : Item("This is the file 'tau'.\n"), 'D/gamma' : Item("This is the file 'gamma'.\n"), - 'D/H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5*,8*'}), + 'D/H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5*,8'}), 'D/H/chi' : Item("This is the file 'chi'.\n"), 'D/H/psi' : Item("New content", - props={SVN_PROP_MERGEINFO : '/A/D/H/psi:5'}), - 'D/H/omega' : Item("New content", - props={SVN_PROP_MERGEINFO : '/A/D/H/omega:8'}), + props={SVN_PROP_MERGEINFO : '/A/D/H/psi:5,8'}), + 'D/H/omega' : Item("New content"), }) expected_skip = wc.State(A_COPY_path, { }) svntest.actions.run_and_verify_merge(A_COPY_path, '4', '8', @@ -5839,7 +5868,6 @@ def merge_to_path_with_switched_children expected_disk, expected_status, expected_skip, None, None, None, None, None, 1) - # Commit changes thus far. expected_output = svntest.wc.State(wc_dir, { 'A_COPY' : Item(verb='Sending'), @@ -5866,17 +5894,16 @@ def merge_to_path_with_switched_children wc_disk.tweak("A_COPY/B/E/beta", contents="New content") wc_disk.tweak("A_COPY/D", - props={SVN_PROP_MERGEINFO : '/A/D:5-6*'}) + props={SVN_PROP_MERGEINFO : '/A/D:5,6*'}) wc_disk.tweak("A_COPY/D/G", props={SVN_PROP_MERGEINFO : '/A/D/G:6*'}) wc_disk.tweak("A_COPY/D/G/rho", contents="New content", props={SVN_PROP_MERGEINFO : '/A/D/G/rho:6'}) wc_disk.tweak("A_COPY/D/H", - props={SVN_PROP_MERGEINFO : '/A/D/H:5*,8*'}) + props={SVN_PROP_MERGEINFO : '/A/D/H:5*,8'}) wc_disk.tweak("A_COPY/D/H/omega", - contents="New content", - props={SVN_PROP_MERGEINFO : '/A/D/H/omega:8'}) + contents="New content") wc_disk.tweak("A_COPY_2", props={}) svntest.actions.run_and_verify_switch(sbox.wc_dir, A_COPY_psi_path, sbox.repo_url + "/A_COPY/D/H/psi", @@ -5915,8 +5942,7 @@ def merge_to_path_with_switched_children expected_disk = wc.State('', { '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5-8'}), 'psi' : Item("New content"), - 'omega' : Item("New content", - props={SVN_PROP_MERGEINFO : '/A/D/H/omega:8'}), + 'omega' : Item("New content"), 'chi' : Item("This is the file 'chi'.\n"), }) expected_skip = wc.State(A_COPY_H_path, { }) @@ -5966,14 +5992,11 @@ def merge_to_path_with_switched_children expected_status_D.tweak('H/psi', wc_rev=10, switched=None) expected_status_D.tweak('H/omega', wc_rev=9) expected_status_D.tweak('G', 'G/rho', switched='S', wc_rev=9) - expected_disk_D.tweak('', props={SVN_PROP_MERGEINFO : '/A/D:5-6*,10*', + expected_disk_D.tweak('', props={SVN_PROP_MERGEINFO : '/A/D:5,6*,10', "prop:name" : "propval"}) expected_disk_D.tweak('G/rho', props={SVN_PROP_MERGEINFO : '/A/D/G/rho:6'}) expected_disk_D.tweak('H', props={SVN_PROP_MERGEINFO : '/A/D/H:5-8'}) - - expected_disk_D.tweak('H/omega', - props={SVN_PROP_MERGEINFO : '/A/D/H/omega:8'}) expected_disk_D.tweak('H/psi', contents="New content", props={}) svntest.actions.run_and_verify_merge(A_COPY_D_path, '9', '10', sbox.repo_url + '/A/D', None, @@ -6034,7 +6057,6 @@ def merge_to_path_with_switched_children 'D/G' : Item(status=' U'), 'D/G/rho' : Item(status=' U'), 'D/H' : Item(status=' U'), - 'D/H/omega' : Item(status=' U'), }) expected_elision_output = wc.State(A_COPY_path, { '' : Item(status=' U'), @@ -6042,7 +6064,6 @@ def merge_to_path_with_switched_children 'D/G' : Item(status=' U'), 'D/G/rho' : Item(status=' U'), 'D/H' : Item(status=' U'), - 'D/H/omega' : Item(status=' U'), }) expected_status = wc.State(A_COPY_path, { '' : Item(status=' M', wc_rev=10), @@ -6063,7 +6084,7 @@ def merge_to_path_with_switched_children 'D/H' : Item(status=' M', wc_rev=10), 'D/H/chi' : Item(status=' ', wc_rev=10), 'D/H/psi' : Item(status='M ', wc_rev=10), - 'D/H/omega' : Item(status='MM', wc_rev=10), + 'D/H/omega' : Item(status='M ', wc_rev=10), }) expected_disk = wc.State('', { 'B' : Item(), @@ -7260,7 +7281,7 @@ def merge_with_depth_files(sbox): # # Test issue #3407 'Shallow merges incorrectly set mergeinfo on children'. @SkipUnless(server_has_mergeinfo) -@Issues(2976,3392,3407) +@Issues(2976,3392,3407,4057) def merge_away_subtrees_noninheritable_ranges(sbox): "subtrees can lose non-inheritable ranges" @@ -7579,8 +7600,9 @@ def merge_away_subtrees_noninheritable_r svntest.actions.run_and_verify_svn(None, None, [], 'revert', '-R', wc_dir) svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir) - # Merge r8 from A/D/H to A_COPY_D/H at depth empty, creating non-inheritable - # mergeinfo on the target. Commit this merge as r13. + # Merge r8 from A/D/H to A_COPY_D/H at depth empty. Since r8 affects only + # A_COPY/D/H itself, the resulting mergeinfo is inheritabled. Commit this + # merge as r13. expected_output = wc.State(H_COPY_2_path, { '' : Item(status=' U'), }) @@ -7596,7 +7618,7 @@ def merge_away_subtrees_noninheritable_r 'chi' : Item(status=' ', wc_rev=12), }) expected_disk = wc.State('', { - '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:8*', + '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:8', "prop:name" : "propval"}), 'psi' : Item("This is the file 'psi'.\n"), 'omega' : Item("This is the file 'omega'.\n"), @@ -9308,6 +9330,7 @@ def ignore_ancestry_and_mergeinfo(sbox): #---------------------------------------------------------------------- @SkipUnless(server_has_mergeinfo) +@Issue(3032) def merge_from_renamed_branch_fails_while_avoiding_repeat_merge(sbox): "merge from renamed branch" #Copy A/C to A/COPY_C results in r2. @@ -9693,7 +9716,7 @@ def new_subtrees_should_not_break_merge( 'D/H/psi' : Item("This is the file 'psi'.\n"), 'D/H/omega' : Item("New content"), 'D/H/nu' : Item("New content", - props={SVN_PROP_MERGEINFO : '/A/D/H/nu:8'}), + props={SVN_PROP_MERGEINFO : '/A/D/H/nu:7-8'}), }) expected_skip = wc.State(A_COPY_path, { }) svntest.actions.run_and_verify_merge(A_COPY_path, '4', '6', @@ -9739,7 +9762,7 @@ def new_subtrees_should_not_break_merge( 'H/psi' : Item("This is the file 'psi'.\n"), 'H/omega' : Item("This is the file 'omega'.\n"), 'H/nu' : Item("New content", - props={SVN_PROP_MERGEINFO : '/A/D/H/nu:8'}), + props={SVN_PROP_MERGEINFO : '/A/D/H/nu:7-8'}), }) expected_skip = wc.State(D_COPY_path, { }) svntest.actions.run_and_verify_merge(D_COPY_path, '6', '5', @@ -9811,7 +9834,7 @@ def new_subtrees_should_not_break_merge( 'D/H/psi' : Item("This is the file 'psi'.\n"), 'D/H/omega' : Item("New content"), 'D/H/nu' : Item("New content", - props={SVN_PROP_MERGEINFO : '/A/D/H/nu:8'}), + props={SVN_PROP_MERGEINFO : '/A/D/H/nu:7-8'}), }) expected_skip = wc.State(A_COPY_path, { }) svntest.actions.run_and_verify_merge(A_COPY_path, '5', '6', @@ -12508,24 +12531,46 @@ def svn_copy(s_rev, path1, path2): svntest.actions.run_and_verify_svn(None, None, [], 'copy', '--parents', '-r', s_rev, path1, path2) -def svn_merge(rev_spec, source, target, exp_out=None, *args): - """Merge a single change from path 'source' to path 'target'. - SRC_CHANGE_NUM is either a number (to cherry-pick that specific change) - or a command-line option revision range string such as '-r10:20'. - *ARGS are additional arguments passed to svn merge.""" +def svn_merge(rev_range, source, target, lines=None, elides=[], + text_conflicts=0, prop_conflicts=0, tree_conflicts=0, args=[]): + """Merge a single change from path SOURCE to path TARGET and verify the + output and that there is no error. (The changes made are not verified.) + + REV_RANGE is either a number (to cherry-pick that specific change) or a + two-element list [X,Y] to pick the revision range '-r(X-1):Y'. + + LINES is a list of regular expressions to match other lines of output; if + LINES is 'None' then match all normal (non-conflicting) merges. + + ELIDES is a list of paths on which mergeinfo elision should be reported. + + TEXT_CONFLICTS, PROP_CONFLICTS and TREE_CONFLICTS specify the number of + each kind of conflict to expect. + + ARGS are additional arguments passed to svn merge.""" + source = local_path(source) target = local_path(target) - if isinstance(rev_spec, int): - rev_spec = '-c' + str(rev_spec) - if exp_out is None: - target_re = re.escape(target) - exp_1 = "--- Merging r.* into '" + target_re + ".*':" - exp_2 = "(A |D |[UG] | [UG]|[UG][UG]) " + target_re + ".*" - exp_3 = "--- Recording mergeinfo for merge of r.* into '" + \ - target_re + ".*':" - exp_out = svntest.verify.RegexOutput(exp_1 + "|" + exp_2 + "|" + exp_3) + elides = [local_path(p) for p in elides] + if isinstance(rev_range, int): + mi_rev_range = [rev_range] + rev_arg = '-c' + str(rev_range) + else: + mi_rev_range = rev_range + rev_arg = '-r' + str(rev_range[0] - 1) + ':' + str(rev_range[1]) + if lines is None: + lines = ["(A |D |[UG] | [UG]|[UG][UG]) " + target + ".*\n"] + else: + # Expect mergeinfo on the target; caller must supply matches for any + # subtree mergeinfo paths. + lines.append(" [UG] " + target + "\n") + exp_out = expected_merge_output([mi_rev_range], lines, target=target, + elides=elides, + text_conflicts=text_conflicts, + prop_conflicts=prop_conflicts, + tree_conflicts=tree_conflicts) svntest.actions.run_and_verify_svn(None, exp_out, [], - 'merge', rev_spec, source, target, *args) + 'merge', rev_arg, source, target, *args) #---------------------------------------------------------------------- # Tests for merging the deletion of a node, where the node to be deleted @@ -12557,7 +12602,8 @@ def del_identical_file(sbox): svn_copy(s_rev_mod, source, target) sbox.simple_commit(target) # Should be deleted quietly. - svn_merge(s_rev_del, source, target, '--- Merging|D |--- Recording| U') + svn_merge(s_rev_del, source, target, + ['D %s\n' % local_path('A/D/G2/tau')]) # Make a differing copy, locally modify it so it's the same, # and merge a deletion to it. @@ -12566,7 +12612,8 @@ def del_identical_file(sbox): sbox.simple_commit(target) svn_modfile(target+"/tau") # Should be deleted quietly. - svn_merge(s_rev_del, source, target, '--- Merging|D |--- Recording| U') + svn_merge(s_rev_del, source, target, + ['D %s\n' % local_path('A/D/G3/tau')]) os.chdir(saved_cwd) @@ -12593,10 +12640,11 @@ def del_sched_add_hist_file(sbox): svn_copy(s_rev_orig, source, target) sbox.simple_commit(target) s_rev = 3 - svn_merge(s_rev_add, source, target, '--- Merging|A |--- Recording| U') + svn_merge(s_rev_add, source, target, + ['A %s\n' % local_path('A/D/G2/file')]) # Should be deleted quietly. svn_merge(-s_rev_add, source, target, - '--- Reverse-merging|D |--- Recording| U| G|--- Eliding') + ['D %s\n' % local_path('A/D/G2/file')], elides=['A/D/G2']) os.chdir(saved_cwd) @@ -12890,12 +12938,6 @@ def merge_target_and_subtrees_need_nonin None, expected_merge_output([[8]], ['U ' + nu_COPY_path + '\n', ' G ' + nu_COPY_path + '\n']), [], 'merge', '-c8', sbox.repo_url + '/A/D/G/nu', nu_COPY_path) - # Replicate pre 1.7 merge behavior where self-referential mergeinfo - # could be inherited, this keeps the original intent of this test intact, - # see http://subversion.tigris.org/issues/show_bug.cgi?id=3669#desc8 - svntest.actions.run_and_verify_svn(None, None, [], 'ps', - SVN_PROP_MERGEINFO, '/A/D/G/nu:2-8', - nu_COPY_path) svntest.actions.run_and_verify_svn( None, expected_merge_output([[-6]], ['G ' + omega_COPY_path + '\n', @@ -13057,15 +13099,9 @@ def merge_two_edits_to_same_prop(sbox): rev4 = initial_rev + 4 # Merge the two changes together to source. - svn_merge('-r'+str(rev3-1)+':'+str(rev4), A_COPY_path, A_path, [ - "--- Merging r9 through r10 into '%s':\n" % A_path, + svn_merge([rev3, rev4], A_COPY_path, A_path, [ " C %s\n" % mu_path, - "--- Recording mergeinfo for merge of r9 through r10 into '%s':\n" \ - % A_path, - " U A\n", - "Summary of conflicts:\n", - " Property conflicts: 1\n"], - '--allow-mixed-revisions') + ], prop_conflicts=1, args=['--allow-mixed-revisions']) # Revert changes to source wc, to test next scenario of #3250 svntest.actions.run_and_verify_svn(None, None, [], @@ -13073,21 +13109,11 @@ def merge_two_edits_to_same_prop(sbox): # Merge the first change, then the second, to source. svn_merge(rev3, A_COPY_path, A_path, [ - "--- Merging r9 into '%s':\n" % A_path, " C %s\n" % mu_path, - "--- Recording mergeinfo for merge of r9 into '%s':\n" % A_path, - " U A\n", - "Summary of conflicts:\n", - " Property conflicts: 1\n"], - '--allow-mixed-revisions') + ], prop_conflicts=1, args=['--allow-mixed-revisions']) svn_merge(rev4, A_COPY_path, A_path, [ - "--- Merging r10 into '%s':\n" % A_path, " C %s\n" % mu_path, - "--- Recording mergeinfo for merge of r10 into '%s':\n" % A_path, - " G A\n", - "Summary of conflicts:\n", - " Property conflicts: 1\n"], - '--allow-mixed-revisions') + ], prop_conflicts=1, args=['--allow-mixed-revisions']) os.chdir(was_cwd) @@ -13136,8 +13162,8 @@ def merge_an_eol_unification_and_set_svn sbox.simple_commit('A_COPY') # Merge the two changes together to the target branch. - svn_merge('-r'+str(rev1)+':'+str(rev3), 'A', 'A_COPY', None, - '--allow-mixed-revisions') + svn_merge([rev2, rev3], 'A', 'A_COPY', + args=['--allow-mixed-revisions']) # That merge should succeed. # Surprise: setting svn:eol-style='LF' instead of 'native' doesn't fail. @@ -16242,7 +16268,8 @@ def merge_with_os_deleted_subtrees(sbox) # Test for issue #3668 'inheritance can result in self-referential # mergeinfo' and issue #3669 'inheritance can result in mergeinfo # describing nonexistent sources' -@Issue(3668) +@Issue(3668,3669) +@XFail() def no_self_referential_or_nonexistent_inherited_mergeinfo(sbox): "don't inherit bogus mergeinfo" @@ -16303,6 +16330,10 @@ def no_self_referential_or_nonexistent_i # Update the WC in preparation for merges. svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir) + # This test is marked as XFail because the following two merges + # create mergeinfo with both non-existent path-revs and self-referential + # mergeinfo. + # # Merge all available revisions from A/C/nu to A_COPY/C/nu. # The target has no explicit mergeinfo of its own but inherits mergeinfo # from A_COPY. A_COPY has the mergeinfo '/A:2-9' so the naive mergeinfo @@ -16680,11 +16711,9 @@ def foreign_repos_prop_conflict(sbox): # Now, merge the propchange to the *second* working copy. expected_output = [' C %s\n' % (os.path.join(other_wc_dir, - "A", "D", "G")), - 'Summary of conflicts:\n', - ' Property conflicts: 1\n', - ] - expected_output = expected_merge_output([[3]], expected_output, True) + "A", "D", "G"))] + expected_output = expected_merge_output([[3]], expected_output, True, + prop_conflicts=1) svntest.actions.run_and_verify_svn(None, expected_output, [], 'merge', '-c3', @@ -16788,11 +16817,15 @@ def merge_adds_subtree_with_mergeinfo(sb 'B/lambda' : Item("This is the file 'lambda'.\n"), 'B/F' : Item(), 'C' : Item(), + # C/nu will pick up the mergeinfo A_COPY/C/nu:8 which is self-referential. + # This is issue #3668 'inheritance can result in self-referential + # mergeinfo', but we'll allow it in this test since issue #3668 is + # tested elsewhere and is not the point of *this* test. 'C/nu' : Item("This is the file 'nu'.\n" \ "More work on the A_COPY branch.\n" \ "A faux conflict resolution.\n", props={SVN_PROP_MERGEINFO : - '/A/C/nu:9-11\n/A_COPY/C/nu:10'}), + '/A/C/nu:9-11\n/A_COPY/C/nu:8,10'}), 'D' : Item(), 'D/G' : Item(), 'D/G/pi' : Item("This is the file 'pi'.\n"), @@ -16818,7 +16851,7 @@ def merge_adds_subtree_with_mergeinfo(sb #---------------------------------------------------------------------- # A test for issue #3978 'reverse merge which adds subtree fails'. -@Issue(3978) +@Issue(3978,4057) @SkipUnless(server_has_mergeinfo) def reverse_merge_adds_subtree(sbox): "reverse merge adds subtree" @@ -16845,6 +16878,9 @@ def reverse_merge_adds_subtree(sbox): 'Cherry-pick r7 from A to A_COPY', wc_dir) # r9 - File depth sync merge from A/D/H to A_COPY/D/H/ + # This shallow merge does not create non-inheritable mergeinfo because of + # the issue #4057 fix; all subtrees affected by the diff are present, so + # non-inheritable mergeinfo is not required. svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir) svntest.actions.run_and_verify_svn(None, None, [], 'merge', sbox.repo_url + '/A/D/H', @@ -16882,7 +16918,6 @@ def reverse_merge_adds_subtree(sbox): # ..\..\..\subversion\libsvn_subr\kitchensink.c:57: (apr_err=200022) # svn: E200022: Negative revision number found parsing '-7' svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir) - svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir) expected_output = wc.State(A_COPY_path, { 'D/H/chi' : Item(status='A '), }) @@ -16931,12 +16966,10 @@ def reverse_merge_adds_subtree(sbox): '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(props={SVN_PROP_MERGEINFO : '/A/D/H:2-6*,8*'}), + 'D/H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:2-6,8'}), 'D/H/chi' : Item("This is the file 'chi'.\n"), - 'D/H/psi' : Item("New content", - props={SVN_PROP_MERGEINFO : '/A/D/H/psi:2-8'}), - 'D/H/omega' : Item("New content", - props={SVN_PROP_MERGEINFO : '/A/D/H/omega:2-8'}), + 'D/H/psi' : Item("New content"), + 'D/H/omega' : Item("New content"), }) expected_skip = wc.State('.', { }) svntest.actions.run_and_verify_merge(A_COPY_path, 7, 6, @@ -17107,6 +17140,215 @@ def record_only_merge_adds_new_subtree_m None, None, None, None, None, 1, False) +#---------------------------------------------------------------------- +# Setup helper for issue #4056 and issue #4057 tests. +def noninheritable_mergeinfo_test_set_up(sbox): + '''Starting with standard greek tree, copy 'A' to 'branch' in r2 and + then made a file edit to A/B/lambda in r3. + Return (expected_output, expected_mergeinfo_output, expected_elision_output, + expected_status, expected_disk, expected_skip) for a merge of + r3 from ^/A/B to branch/B.''' + + sbox.build() + wc_dir = sbox.wc_dir + + lambda_path = os.path.join(wc_dir, 'A', 'B', 'lambda') + B_branch_path = os.path.join(wc_dir, 'branch', 'B') + + # r2 - Branch ^/A to ^/branch. + svntest.main.run_svn(None, 'copy', sbox.repo_url + '/A', + sbox.repo_url + '/branch', '-m', 'make a branch') + + # r3 - Make an edit to A/B/lambda. + svntest.main.file_write(lambda_path, "trunk edit.\n") + svntest.main.run_svn(None, 'commit', '-m', 'file edit', wc_dir) + svntest.main.run_svn(None, 'up', wc_dir) + + expected_output = wc.State(B_branch_path, { + 'lambda' : Item(status='U '), + }) + expected_mergeinfo_output = wc.State(B_branch_path, { + '' : Item(status=' U'), + 'lambda' : Item(status=' U'), + }) + expected_elision_output = wc.State(B_branch_path, { + 'lambda' : Item(status=' U'), + }) + expected_status = wc.State(B_branch_path, { + '' : Item(status=' M'), + 'lambda' : Item(status='M '), + 'E' : Item(status=' '), + 'E/alpha' : Item(status=' '), + 'E/beta' : Item(status=' '), + 'F' : Item(status=' '), + }) + expected_status.tweak(wc_rev='3') + expected_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:3'}), + 'lambda' : Item("trunk edit.\n"), + 'E' : Item(), + 'E/alpha' : Item("This is the file 'alpha'.\n"), + 'E/beta' : Item("This is the file 'beta'.\n"), + 'F' : Item(), + }) + expected_skip = wc.State(B_branch_path, {}) + + return expected_output, expected_mergeinfo_output, expected_elision_output, \ + expected_status, expected_disk, expected_skip + + +#---------------------------------------------------------------------- +# Test for issue #4056 "don't record non-inheritable mergeinfo if missing +# subtrees are not touched by the full-depth diff". +@Issue(4056) +@SkipUnless(server_has_mergeinfo) +def unnecessary_noninheritable_mergeinfo_missing_subtrees(sbox): + "missing subtrees untouched by infinite depth merge" + + B_branch_path = os.path.join(sbox.wc_dir, 'branch', 'B') + + # Setup a simple branch to which + expected_output, expected_mergeinfo_output, expected_elision_output, \ + expected_status, expected_disk, expected_skip = \ + noninheritable_mergeinfo_test_set_up(sbox) + + # Create a shallow merge target; set depth of branch/B to files. + svntest.main.run_svn(None, 'up', '--set-depth=files', B_branch_path) + expected_status.remove('E', 'E/alpha', 'E/beta', 'F') + expected_disk.remove('E', 'E/alpha', 'E/beta', 'F') + + # Merge r3 from ^/A/B to branch/B + # + # Merge is smart enough to realize that despite the shallow merge target, + # the diff can only affect branch/B/lambda, which is still present, so there + # is no need to record non-inheritable mergeinfo on the target + # or any subtree mergeinfo whatsoever: + # + # >svn pg svn:mergeinfo -vR + # Properties on 'branch\B': + # svn:mergeinfo + # /A/B:3 <-- Nothing was skipped, so doesn't need + # to be non-inheritable. + svntest.actions.run_and_verify_merge(B_branch_path, + '2', '3', + sbox.repo_url + '/A/B', None, + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip, + None, None, None, None, None, 1, 1, + B_branch_path) + +#---------------------------------------------------------------------- +# Test for issue #4057 "don't record non-inheritable mergeinfo in shallow +# merge if entire diff is within requested depth". +@Issue(4057) +@SkipUnless(server_has_mergeinfo) +def unnecessary_noninheritable_mergeinfo_shallow_merge(sbox): + "shallow merge reaches all necessary subtrees" + + B_branch_path = os.path.join(sbox.wc_dir, 'branch', 'B') + E_path = os.path.join(sbox.wc_dir, 'A', 'B', 'E') + + # Setup a simple branch to which + expected_output, expected_mergeinfo_output, expected_elision_output, \ + expected_status, expected_disk, expected_skip = \ + noninheritable_mergeinfo_test_set_up(sbox) + + # Merge r3 from ^/A/B to branch/B at operational depth=files + # + # Previously this failed because merge wasn't smart enough to + # realize that despite being a shallow merge, the diff can + # only affect branch/B/lambda, which is within the specified + # depth, so there is no need to record non-inheritable mergeinfo + # or subtree mergeinfo: + # + # >svn pg svn:mergeinfo -vR + # Properties on 'branch\B': + # svn:mergeinfo + # /A/B:3* <-- Should be inheritable + # Properties on 'branch\B\lambda': + # svn:mergeinfo + # /A/B/lambda:3 <-- Not necessary + expected_skip = wc.State(B_branch_path, {}) + svntest.actions.run_and_verify_merge(B_branch_path, '2', '3', + sbox.repo_url + '/A/B', None, + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip, + None, None, None, None, None, 1, 1, + '--depth', 'files', B_branch_path) + + # Revert the merge and then make a prop change to A/B/E in r4. + svntest.actions.run_and_verify_svn(None, None, [], + 'revert', '--recursive', sbox.wc_dir) + svntest.actions.run_and_verify_svn(None, + ["property 'prop:name' set on '" + + E_path + "'\n"], [], 'ps', + 'prop:name', 'propval', E_path) + svntest.actions.run_and_verify_svn(None, None, [], + 'ci', '-m', 'A new property on a dir', + sbox.wc_dir) + svntest.actions.run_and_verify_svn(None, None, [], + 'up', sbox.wc_dir) + + # Merge r4 from ^/A/B to branch/B at operational depth=immediates + # + # Previously this failed because the mergetracking logic didn't realize + # that despite being a shallow merge, the diff only affected branch/B/E, + # which was within the specified depth, so there was no need to record + # non-inheritable mergeinfo or subtree mergeinfo: + # + # >svn pg svn:mergeinfo -vR + # Properties on 'branch\B': + # svn:mergeinfo + # /A/B:4* <-- Should be inheritable + # Properties on 'branch\B\E': + # svn:mergeinfo + # /A/B/E:4 <-- Not necessary + expected_output = wc.State(B_branch_path, { + 'E' : Item(status=' U'), + }) + expected_mergeinfo_output = wc.State(B_branch_path, { + '' : Item(status=' U'), + 'E' : Item(status=' U'), + }) + expected_elision_output = wc.State(B_branch_path, { + 'E' : Item(status=' U'), + }) + expected_status = wc.State(B_branch_path, { + '' : Item(status=' M'), + 'lambda' : Item(status=' '), + 'E' : Item(status=' M'), + 'E/alpha' : Item(status=' '), + 'E/beta' : Item(status=' '), + 'F' : Item(status=' '), + }) + expected_status.tweak(wc_rev='4') + expected_disk = wc.State('', { + '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:4'}), + 'lambda' : Item("This is the file 'lambda'.\n"), + 'E' : Item(props={'prop:name' : 'propval'}), + 'E/alpha' : Item("This is the file 'alpha'.\n"), + 'E/beta' : Item("This is the file 'beta'.\n"), + 'F' : Item(), + }) + svntest.actions.run_and_verify_merge(B_branch_path, '3', '4', + sbox.repo_url + '/A/B', None, + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip, + None, None, None, None, None, 1, 1, + '--depth', 'immediates', B_branch_path) + ######################################################################## # Run the tests @@ -17235,6 +17477,8 @@ test_list = [ None, reverse_merge_adds_subtree, merged_deletion_causes_tree_conflict, record_only_merge_adds_new_subtree_mergeinfo, + unnecessary_noninheritable_mergeinfo_missing_subtrees, + unnecessary_noninheritable_mergeinfo_shallow_merge, ] if __name__ == '__main__': Modified: subversion/branches/revprop-packing/subversion/tests/cmdline/merge_tree_conflict_tests.py URL: http://svn.apache.org/viewvc/subversion/branches/revprop-packing/subversion/tests/cmdline/merge_tree_conflict_tests.py?rev=1231318&r1=1231317&r2=1231318&view=diff ============================================================================== --- subversion/branches/revprop-packing/subversion/tests/cmdline/merge_tree_conflict_tests.py (original) +++ subversion/branches/revprop-packing/subversion/tests/cmdline/merge_tree_conflict_tests.py Fri Jan 13 21:40:26 2012 @@ -46,6 +46,7 @@ from svntest.main import server_has_merg from merge_tests import set_up_branch from merge_tests import svn_copy from merge_tests import svn_merge +from merge_tests import expected_merge_output #---------------------------------------------------------------------- @SkipUnless(server_has_mergeinfo) @@ -680,25 +681,16 @@ def del_differing_file(sbox): 'newprop', 'v', target+"/pi") dir_D = os.path.join('A','D') - dir_G2 = os.path.join(dir_D, 'G2') tau = os.path.join(dir_D,'G2','tau') pi = os.path.join(dir_D, 'G2', 'pi') # Should complain and "skip" it. svn_merge(s_rev_tau, source, target, [ - "--- Merging r2 into '%s':\n" % dir_G2, - " C %s\n" % tau, - "--- Recording mergeinfo for merge of r2 into '%s':\n" % (dir_G2), - " U %s\n" % (dir_G2), - "Summary of conflicts:\n", - " Tree conflicts: 1\n"]) + " C %s\n" % tau, # merge + ], tree_conflicts=1) svn_merge(s_rev_pi, source, target, [ - "--- Merging r3 into '%s':\n" % dir_G2, - " C %s\n" % pi, - "--- Recording mergeinfo for merge of r3 into '%s':\n" % (dir_G2), - " G %s\n" % (dir_G2), - "Summary of conflicts:\n", - " Tree conflicts: 1\n"]) + " C %s\n" % pi, # merge + ], tree_conflicts=1) # Copy a file, modify it, commit, and merge a deletion to it. @@ -710,26 +702,17 @@ def del_differing_file(sbox): sbox.simple_commit(target) - dir_G3 = os.path.join(dir_D, 'G3') tau = os.path.join(dir_D,'G3','tau') pi = os.path.join(dir_D, 'G3', 'pi') # Should complain and "skip" it. svn_merge(s_rev_tau, source, target, [ - "--- Merging r2 into '%s':\n" % dir_G3, " C %s\n" % tau, - "--- Recording mergeinfo for merge of r2 into '%s':\n" % (dir_G3), - " U %s\n" % (dir_G3), - "Summary of conflicts:\n", - " Tree conflicts: 1\n"]) + ], tree_conflicts=1) svn_merge(s_rev_pi, source, target, [ - "--- Merging r3 into '%s':\n" % dir_G3, " C %s\n" % pi, - "--- Recording mergeinfo for merge of r3 into '%s':\n" % (dir_G3), - " G %s\n" % (dir_G3), - "Summary of conflicts:\n", - " Tree conflicts: 1\n"]) + ], tree_conflicts=1) os.chdir(saved_cwd) @@ -1738,18 +1721,15 @@ def merge_replace_causes_tree_conflict(s 'propname', 'propval', A_D_H) # svn merge $URL/A $URL/branch A - expected_stdout = verify.UnorderedOutput([ - "--- Merging differences between repository URLs into '" + A + "':\n", + expected_stdout = expected_merge_output(None, [ + # merge ' C ' + A_B_E + '\n', ' C ' + A_mu + '\n', ' C ' + A_D_G_pi + '\n', ' C ' + A_D_H + '\n', - "--- Recording mergeinfo for merge between repository URLs into '" \ - + A + "':\n", + # mergeinfo ' U ' + A + '\n', - 'Summary of conflicts:\n', - ' Tree conflicts: 4\n', - ]) + ], target=A, two_url=True, tree_conflicts=4) actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'merge', url_A, url_branch, A) @@ -1829,15 +1809,10 @@ def merge_replace_causes_tree_conflict2( ### A file-with-file replacement onto a deleted file. # svn merge $URL/A/mu $URL/branch/mu A/mu - expected_stdout = verify.UnorderedOutput([ - "--- Merging differences between repository URLs into '" + A + "':\n", - ' C ' + A_mu + '\n', - "--- Recording mergeinfo for merge between repository URLs into '" + - A + "':\n", - " U " + A + "\n", - 'Summary of conflicts:\n', - ' Tree conflicts: 1\n', - ]) + expected_stdout = expected_merge_output(None, [ + ' C ' + A_mu + '\n', # merge + " U " + A + "\n", # mergeinfo + ], target=A, two_url=True, tree_conflicts=1) actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'merge', url_A, url_branch, A, '--depth=files') @@ -1853,15 +1828,10 @@ def merge_replace_causes_tree_conflict2( ### A dir-with-dir replacement onto a deleted directory. # svn merge $URL/A/B $URL/branch/B A/B - expected_stdout = verify.UnorderedOutput([ - "--- Merging differences between repository URLs into '" + A_B + "':\n", - ' C ' + A_B_E + '\n', - "--- Recording mergeinfo for merge between repository URLs into '" + - A_B + "':\n", - " U " + A_B + "\n", - 'Summary of conflicts:\n', - ' Tree conflicts: 1\n', - ]) + expected_stdout = expected_merge_output(None, [ + ' C ' + A_B_E + '\n', # merge + " U " + A_B + "\n", # mergeinfo + ], target=A_B, two_url=True, tree_conflicts=1) actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'merge', url_A_B, url_branch_B, A_B) @@ -1877,16 +1847,11 @@ def merge_replace_causes_tree_conflict2( ### A dir-with-file replacement onto a deleted directory. # svn merge --depth=immediates $URL/A/D $URL/branch/D A/D - expected_stdout = verify.UnorderedOutput([ - "--- Merging differences between repository URLs into '" + A_D + "':\n", - ' C ' + A_D_H + '\n', - "--- Recording mergeinfo for merge between repository URLs into '" + - A_D + "':\n", - " U " + A_D + "\n", + expected_stdout = expected_merge_output(None, [ + ' C ' + A_D_H + '\n', # merge + " U " + A_D + "\n", # mergeinfo " U " + A_D_G + "\n", - 'Summary of conflicts:\n', - ' Tree conflicts: 1\n', - ]) + ], target=A_D, two_url=True, tree_conflicts=1) actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'merge', '--depth=immediates', url_A_D, url_branch_D, A_D) @@ -1902,20 +1867,9 @@ def merge_replace_causes_tree_conflict2( ### A file-with-dir replacement onto a deleted file. # svn merge $URL/A/D/G $URL/branch/D/G A/D/G - expected_stdout = verify.UnorderedOutput([ - "--- Merging differences between repository URLs into '" + A_D_G + - "':\n", - ' C ' + A_D_G_pi + '\n', - "--- Recording mergeinfo for merge between repository URLs into '" + - A_D_G + "':\n", - "--- Eliding mergeinfo from '" + A_D_G_pi + "':\n", - " U " + A_D_G_pi + "\n", - "--- Eliding mergeinfo from '" + A_D_G_pi + "':\n", - " U " + A_D_G_pi + "\n", - " G " + A_D_G + "\n", - 'Summary of conflicts:\n', - ' Tree conflicts: 1\n', - ]) + expected_stdout = expected_merge_output(None, [ + ' C ' + A_D_G_pi + '\n', # merge + ], target=A_D_G, elides=[A_D_G_pi, A_D_G], two_url=True, tree_conflicts=1) actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'merge', url_A_D_G, url_branch_D_G, A_D_G) @@ -1940,6 +1894,77 @@ def merge_replace_causes_tree_conflict2( actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'st', '--depth=empty', path) +#---------------------------------------------------------------------- +# Test for issue #4011 'merge of replacement on local delete fails' +@SkipUnless(server_has_mergeinfo) +@Issue(4011) +@XFail() +def merge_replace_on_del_fails(sbox): + "merge replace on local delete fails" + + sbox.build() + wc_dir = sbox.wc_dir + + C_path = os.path.join(wc_dir, 'A', 'C') + branch_path = os.path.join(wc_dir, 'branch') + C_branch_path = os.path.join(wc_dir, 'branch', 'C') + + # r2 - Copy ^/A to ^/branch + svntest.actions.run_and_verify_svn(None, None, [], 'copy', + sbox.repo_url + '/A', + sbox.repo_url + '/branch', + '-m', 'Create a branch') + + # r3 - Replace A/C + svntest.actions.run_and_verify_svn(None, None, [], 'del', C_path) + svntest.actions.run_and_verify_svn(None, None, [], 'mkdir', C_path) + svntest.actions.run_and_verify_svn(None, None, [], 'ci', + '-m', 'Replace A/C', wc_dir) + + # r4 - Delete branch/C + svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir) + svntest.actions.run_and_verify_svn(None, None, [], 'del', C_branch_path) + svntest.actions.run_and_verify_svn(None, None, [], 'ci', + '-m', 'Delete branch/C', wc_dir) + + # Sync merge ^/A to branch + svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir) + expected_stdout = expected_merge_output([[2,4]], [ + ' C ' + C_branch_path + '\n', # merge + ' U ' + branch_path + '\n', # mergeinfo + ], target=branch_path, tree_conflicts=1) + # This currently fails with: + # + # >svn merge ^/A branch + # ..\..\..\subversion\svn\util.c:913: (apr_err=155010) + # ..\..\..\subversion\libsvn_client\merge.c:11349: (apr_err=155010) + # ..\..\..\subversion\libsvn_client\merge.c:11303: (apr_err=155010) + # ..\..\..\subversion\libsvn_client\merge.c:11303: (apr_err=155010) + # ..\..\..\subversion\libsvn_client\merge.c:11273: (apr_err=155010) + # ..\..\..\subversion\libsvn_client\merge.c:9287: (apr_err=155010) + # ..\..\..\subversion\libsvn_client\merge.c:8870: (apr_err=155010) + # ..\..\..\subversion\libsvn_client\merge.c:5349: (apr_err=155010) + # ..\..\..\subversion\libsvn_repos\reporter.c:1430: (apr_err=155010) + # ..\..\..\subversion\libsvn_client\ra.c:247: (apr_err=155010) + # ..\..\..\subversion\libsvn_repos\reporter.c:1269: (apr_err=155010) + # ..\..\..\subversion\libsvn_repos\reporter.c:1205: (apr_err=155010) + # ..\..\..\subversion\libsvn_repos\reporter.c:920: (apr_err=155010) + # ..\..\..\subversion\libsvn_delta\cancel.c:120: (apr_err=155010) + # ..\..\..\subversion\libsvn_delta\cancel.c:120: (apr_err=155010) + # ..\..\..\subversion\libsvn_client\repos_diff.c:710: (apr_err=155010) + # ..\..\..\subversion\libsvn_client\merge.c:2234: (apr_err=155010) + # ..\..\..\subversion\libsvn_wc\adm_ops.c:1069: (apr_err=155010) + # ..\..\..\subversion\libsvn_wc\adm_ops.c:956: (apr_err=155010) + # ..\..\..\subversion\libsvn_wc\update_editor.c:5036: (apr_err=155010) + # ..\..\..\subversion\libsvn_wc\wc_db.c:6985: (apr_err=155010) + # ..\..\..\subversion\libsvn_wc\wc_db.c:6929: (apr_err=155010) + # ..\..\..\subversion\libsvn_wc\wc_db.c:6920: (apr_err=155010) + # svn: E155010: The node 'C:\SVN\src-trunk\Debug\subversion\tests\ + # cmdline\svn-test-work\working_copies\merge_tree_conflict_tests-24\ + # branch\C' was not found. + actions.run_and_verify_svn2('OUTPUT', expected_stdout, [], 0, 'merge', + sbox.repo_url + '/A', branch_path) + ######################################################################## # Run the tests @@ -1969,6 +1994,7 @@ test_list = [ None, tree_conflicts_merge_del_onto_missing, merge_replace_causes_tree_conflict, merge_replace_causes_tree_conflict2, + merge_replace_on_del_fails, ] if __name__ == '__main__': Modified: subversion/branches/revprop-packing/subversion/tests/cmdline/mergeinfo_tests.py URL: http://svn.apache.org/viewvc/subversion/branches/revprop-packing/subversion/tests/cmdline/mergeinfo_tests.py?rev=1231318&r1=1231317&r2=1231318&view=diff ============================================================================== --- subversion/branches/revprop-packing/subversion/tests/cmdline/mergeinfo_tests.py (original) +++ subversion/branches/revprop-packing/subversion/tests/cmdline/mergeinfo_tests.py Fri Jan 13 21:40:26 2012 @@ -68,8 +68,11 @@ def no_mergeinfo(sbox): "'mergeinfo' on a URL that lacks mergeinfo" sbox.build(create_wc=False) + sbox.simple_repo_copy('A', 'A2') svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""), - [], sbox.repo_url, sbox.repo_url) + [], + sbox.repo_url + '/A', + sbox.repo_url + '/A2') def mergeinfo(sbox): "'mergeinfo' on a path with mergeinfo" @@ -77,41 +80,65 @@ def mergeinfo(sbox): sbox.build() wc_dir = sbox.wc_dir + # make a branch 'A2' + sbox.simple_repo_copy('A', 'A2') # r2 + # make a change in branch 'A' + sbox.simple_mkdir('A/newdir') + sbox.simple_commit() # r3 + sbox.simple_update() + # Dummy up some mergeinfo. - svntest.actions.run_and_verify_svn(None, None, [], 'ps', SVN_PROP_MERGEINFO, - '/:1', wc_dir) + svntest.actions.run_and_verify_svn(None, None, [], + 'ps', SVN_PROP_MERGEINFO, '/A:3', + sbox.ospath('A2')) svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""), - ['1'], sbox.repo_url, wc_dir) + ['3'], + sbox.repo_url + '/A', + sbox.ospath('A2')) @SkipUnless(server_has_mergeinfo) def explicit_mergeinfo_source(sbox): "'mergeinfo' with source selection" + # The idea is the target has mergeinfo pertaining to two or more different + # source branches and we're asking about just one of them. + sbox.build() - wc_dir = sbox.wc_dir - H_path = os.path.join(wc_dir, 'A', 'D', 'H') - H2_path = os.path.join(wc_dir, 'A', 'D', 'H2') - B_url = sbox.repo_url + '/A/B' - B_path = os.path.join(wc_dir, 'A', 'B') - G_url = sbox.repo_url + '/A/D/G' - G_path = os.path.join(wc_dir, 'A', 'D', 'G') - H2_url = sbox.repo_url + '/A/D/H2' - # Make a copy, and dummy up some mergeinfo. - mergeinfo = '/A/B:1\n/A/D/G:1\n' - svntest.actions.set_prop(SVN_PROP_MERGEINFO, mergeinfo, H_path) - svntest.main.run_svn(None, "cp", H_path, H2_path) - svntest.main.run_svn(None, "ci", "-m", "r2", wc_dir) + def url(relpath): + return sbox.repo_url + '/' + relpath + def path(relpath): + return sbox.ospath(relpath) + + B = 'A/B' + + # make some branches + B2 = 'A/B2' + B3 = 'A/B3' + sbox.simple_repo_copy(B, B2) # r2 + sbox.simple_repo_copy(B, B3) # r3 + sbox.simple_update() + + # make changes in the branches + sbox.simple_mkdir('A/B2/newdir') + sbox.simple_commit() # r4 + sbox.simple_mkdir('A/B3/newdir') + sbox.simple_commit() # r5 + + # Put dummy mergeinfo on branch root + mergeinfo = '/A/B2:2-5\n/A/B3:2-5\n' + sbox.simple_propset(SVN_PROP_MERGEINFO, mergeinfo, B) + sbox.simple_commit() # Check using each of our recorded merge sources (as paths and URLs). svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""), - ['1'], B_url, H_path) + ['2', '4'], url(B2), path(B)) svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""), - ['1'], B_path, H_path) + ['2', '4'], path(B2), path(B)) svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""), - ['1'], G_url, H_path) + ['3', '5'], url(B3), path(B)) svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""), - ['1'], G_path, H_path) + ['3', '5'], path(B3), path(B)) @SkipUnless(server_has_mergeinfo) def mergeinfo_non_source(sbox): @@ -624,6 +651,109 @@ def wc_target_inherits_mergeinfo_from_re test_svn_mergeinfo_4_way(D_COPY_path) test_svn_mergeinfo_4_way(subtree_wc) +#---------------------------------------------------------------------- +# A test for issue 3791 'svn mergeinfo shows natural history of added +# subtrees as eligible'. +@Issue(3791) +@SkipUnless(server_has_mergeinfo) +def natural_history_is_not_eligible_nor_merged(sbox): + "natural history is not eligible nor merged" + + sbox.build() + wc_dir = sbox.wc_dir + wc_disk, wc_status = set_up_branch(sbox) + + nu_path = os.path.join(wc_dir, 'A', 'C', 'nu') + A_COPY_path = os.path.join(wc_dir, 'A_COPY') + nu_COPY_path = os.path.join(wc_dir, 'A_COPY', 'C', 'nu') + + # r7 - Add a new file A/C/nu + svntest.main.file_write(nu_path, "This is the file 'nu'.\n") + svntest.actions.run_and_verify_svn(None, None, [], 'add', nu_path) + svntest.actions.run_and_verify_svn(None, None, [], 'ci', + '-m', 'Add a file', wc_dir) + + # r8 - Sync merge ^/A to A_COPY + svntest.actions.run_and_verify_svn(None, None, [], 'merge', + sbox.repo_url + '/A', A_COPY_path) + svntest.actions.run_and_verify_svn(None, None, [], 'ci', + '-m', 'Add a file', wc_dir) + + # r9 - Modify the file added in r7 + svntest.main.file_write(nu_path, "Modification to file 'nu'.\n") + svntest.actions.run_and_verify_svn(None, None, [], 'ci', + '-m', 'Modify added file', wc_dir) + + # r10 - Merge ^/A/C/nu to A_COPY/C/nu, creating subtree mergeinfo. + svntest.actions.run_and_verify_svn(None, None, [], 'merge', + sbox.repo_url + '/A/C/nu', nu_COPY_path) + svntest.actions.run_and_verify_svn(None, None, [], 'ci', + '-m', 'Add a file', wc_dir) + svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir) + + # We've effectively merged everything from ^/A to A_COPY, check + # that svn mergeinfo -R agrees. + # + # First check if there are eligible revisions, there should be none. + svntest.actions.run_and_verify_mergeinfo( + adjust_error_for_server_version(''), + [], sbox.repo_url + '/A', + A_COPY_path, '--show-revs', 'eligible', '-R') + + # Now check that all operative revisions show as merged. + svntest.actions.run_and_verify_mergeinfo( + adjust_error_for_server_version(''), + ['3','4','5','6','7','9'], sbox.repo_url + '/A', + A_COPY_path, '--show-revs', 'merged', '-R') + +#---------------------------------------------------------------------- +# A test for issue 4050 "'svn mergeinfo' always considers non-inheritable +# ranges as partially merged". +@Issue(4050) +@SkipUnless(server_has_mergeinfo) +def noninheritabled_mergeinfo_not_always_eligible(sbox): + "noninheritabled mergeinfo not always eligible" + + sbox.build() + wc_dir = sbox.wc_dir + + A_path = os.path.join(wc_dir, 'A') + branch_path = os.path.join(wc_dir, 'branch') + + # r2 - Branch ^/A to ^/branch. + svntest.main.run_svn(None, 'copy', sbox.repo_url + '/A', + sbox.repo_url + '/branch', '-m', 'make a branch') + + # r3 - Make prop edit to A. + svntest.main.run_svn(None, 'ps', 'prop', 'val', A_path) + svntest.main.run_svn(None, 'commit', '-m', 'file edit', wc_dir) + svntest.main.run_svn(None, 'up', wc_dir) + + # r4 - Merge r3 from ^/A to branch at depth=empty. + svntest.actions.run_and_verify_svn(None, None, [], 'merge', + sbox.repo_url + '/A', branch_path, + '-c3', '--depth=empty') + # Forcibly set non-inheritable mergeinfo to replicate the pre-1.8 behavior, + # where prior to the fix for issue #4057, non-inheritable mergeinfo was + # unconditionally set for merges with shallow operational depths. + svntest.actions.run_and_verify_svn(None, None, [], + 'propset', SVN_PROP_MERGEINFO, + '/A:3*\n', branch_path) + svntest.main.run_svn(None, 'commit', '-m', 'shallow merge', wc_dir) + + # Now check that r3 is reported as fully merged from ^/A to ^/branch + # and does not show up all when asking for eligible revs. + svntest.actions.run_and_verify_mergeinfo( + adjust_error_for_server_version(''), + ['3'], sbox.repo_url + '/A', sbox.repo_url + '/branch', + '--show-revs', 'merged', '-R') + # Likewise r3 shows up as partially eligible when asking about + # for --show-revs=eligible. + svntest.actions.run_and_verify_mergeinfo( + adjust_error_for_server_version(''), + [], sbox.repo_url + '/A', sbox.repo_url + '/branch', + '--show-revs', 'eligible', '-R') + ######################################################################## # Run the tests @@ -639,6 +769,8 @@ test_list = [ None, recursive_mergeinfo, mergeinfo_on_pegged_wc_path, wc_target_inherits_mergeinfo_from_repos, + natural_history_is_not_eligible_nor_merged, + noninheritabled_mergeinfo_not_always_eligible, ] if __name__ == '__main__':