#!/usr/bin/env python # # merge_authz_tests.py: merge tests that need to write an authz file # # 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 wc # (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 from svntest.mergetrees import set_up_branch from svntest.mergetrees import expected_merge_output from svntest.main import SVN_PROP_MERGEINFO from svntest.main import write_restrictive_svnserve_conf from svntest.main import write_authz_file from svntest.main import is_ra_type_dav from svntest.main import is_ra_type_svn from svntest.main import server_has_mergeinfo from svntest.actions import fill_file_with_lines from svntest.actions import make_conflict_marker_text from svntest.actions import inject_conflict_into_expected_state ###################################################################### # Tests # # Each test must return on success or raise on failure. #---------------------------------------------------------------------- # Test for issues # # #2893 - Handle merge info for portions of a tree not checked out due # to insufficient authz. # # #2997 - If skipped paths come first in operative merge mergeinfo # is incomplete # # #2829 - Improve handling for skipped paths encountered during a merge. # 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. # # #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): "skipped paths get overriding mergeinfo" # Test that we override the mergeinfo for child paths which weren't # actually merged because they were skipped. # # This test covers paths skipped because: # # 1) The source of a merge is inaccessible due to authz restrictions. # 2) Destination of merge is inaccessible due to authz restrictions. # 3) Source *and* destination of merge is inaccessible due to authz # restrictions. sbox.build() wc_dir = sbox.wc_dir wc_disk, wc_status = set_up_branch(sbox, False, 3) # Create a restrictive authz where part of the merge source and part # of the target are inaccesible. write_restrictive_svnserve_conf(sbox.repo_dir) write_authz_file(sbox, {"/" : svntest.main.wc_author +"=rw", # Make a directory in the merge source inaccessible. "/A/B/E" : svntest.main.wc_author + "=", # Make a file and dir in the merge destination # inaccessible. "/A_COPY_2/D/H/psi" : svntest.main.wc_author + "=", "/A_COPY_2/D/G" : svntest.main.wc_author + "=", # Make the source and destination inaccessible. "/A_COPY_3/B/E" : svntest.main.wc_author + "=", }) # Checkout just the branch under the newly restricted authz. wc_restricted = sbox.add_wc_path('restricted') svntest.actions.run_and_verify_svn(None, [], 'checkout', sbox.repo_url, wc_restricted) # Some paths we'll use in the second WC. A_COPY_path = os.path.join(wc_restricted, "A_COPY") A_COPY_2_path = os.path.join(wc_restricted, "A_COPY_2") A_COPY_2_H_path = os.path.join(wc_restricted, "A_COPY_2", "D", "H") A_COPY_3_path = os.path.join(wc_restricted, "A_COPY_3") omega_path = os.path.join(wc_restricted, "A_COPY", "D", "H", "omega") zeta_path = sbox.ospath("A/D/H/zeta") # Merge r4:8 into the restricted WC's A_COPY. # # We expect A_COPY/B/E to be skipped because we can't access the source # and A_COPY/D/H/omega because it is missing. Since we have A_COPY/B/E # we should override it's inherited mergeinfo, giving it just what it # inherited from A_COPY before the merge. expected_output = wc.State(A_COPY_path, { 'D/G/rho' : Item(status='U '), 'D/H/psi' : Item(status='U '), 'D/H/omega' : Item(status='U '), }) expected_mergeinfo_output = wc.State(A_COPY_path, { '' : Item(status=' U'), 'B/E' : Item(status=' U'), }) expected_elision_output = wc.State(A_COPY_path, { }) expected_status = wc.State(A_COPY_path, { '' : Item(status=' M', wc_rev=8), 'D/H/chi' : Item(status=' ', wc_rev=8), 'D/H/psi' : Item(status='M ', wc_rev=8), 'D/H/omega' : Item(status='M ', wc_rev=8), 'D/H' : Item(status=' ', wc_rev=8), 'D/G/pi' : Item(status=' ', wc_rev=8), 'D/G/rho' : Item(status='M ', wc_rev=8), 'D/G/tau' : Item(status=' ', wc_rev=8), 'D/G' : Item(status=' ', wc_rev=8), 'D/gamma' : Item(status=' ', wc_rev=8), 'D' : Item(status=' ', wc_rev=8), 'B/lambda' : Item(status=' ', wc_rev=8), 'B/E' : Item(status=' M', 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), 'B' : Item(status=' ', wc_rev=8), 'mu' : Item(status=' ', wc_rev=8), 'C' : Item(status=' ', wc_rev=8), }) expected_disk = wc.State('', { '' : Item(props={SVN_PROP_MERGEINFO : '/A:5-8'}), 'D/H/psi' : Item("New content"), 'D/H/chi' : Item("This is the file 'chi'.\n"), 'D/H/omega' : Item("New content"), 'D/H' : 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' : Item(), 'D/gamma' : Item("This is the file 'gamma'.\n"), 'D' : Item(), 'B/lambda' : Item("This is the file 'lambda'.\n"), 'B/E' : Item(props={SVN_PROP_MERGEINFO : ''}), 'B/E/alpha' : Item("This is the file 'alpha'.\n"), 'B/E/beta' : Item("This is the file 'beta'.\n"), 'B/F' : Item(), 'B' : Item(), 'mu' : Item("This is the file 'mu'.\n"), 'C' : Item(), }) expected_skip = wc.State(A_COPY_path, { 'B/E' : Item(verb='Skipped missing target'), }) svntest.actions.run_and_verify_merge(A_COPY_path, '4', '8', sbox.repo_url + '/A', None, expected_output, expected_mergeinfo_output, expected_elision_output, expected_disk, expected_status, expected_skip, check_props=True) # Merge r4:8 into the restricted WC's A_COPY_2. # # As before we expect A_COPY_2/B/E to be skipped because we can't access the # source but now the destination paths A_COPY_2/D/G, A_COPY_2/D/G/rho, and # A_COPY_2/D/H/psi should also be skipped because our test user doesn't have # access. # # After the merge the parents of the missing dest paths, A_COPY_2/D and # A_COPY_2/D/H get non-inheritable mergeinfo. Those parents' children that # *are* present and are affected by the merge, only A_COPY_2/D/H/omega in # this case, get their own mergeinfo. Note that A_COPY_2/D/H is both the # parent of a missing child and the sibling of missing child, but the former # always takes precedence in terms of getting *non*-inheritable mergeinfo. expected_output = wc.State(A_COPY_2_path, { 'D/H/omega' : Item(status='U '), # Below the skip 'D/G/rho' : Item(status=' ', treeconflict='U'), }) expected_mergeinfo_output = wc.State(A_COPY_2_path, { '' : Item(status=' U'), 'D' : Item(status=' U'), 'D/H' : Item(status=' U'), 'D/H/omega' : Item(status=' U'), 'B/E' : Item(status=' U'), }) expected_elision_output = wc.State(A_COPY_2_path, { }) expected_status = wc.State(A_COPY_2_path, { '' : Item(status=' M', wc_rev=8), 'D/H/chi' : Item(status=' ', wc_rev=8), 'D/H/omega' : Item(status='MM', wc_rev=8), 'D/H' : Item(status=' M', wc_rev=8), 'D/gamma' : Item(status=' ', wc_rev=8), 'D' : Item(status=' M', wc_rev=8), 'B/lambda' : Item(status=' ', wc_rev=8), 'B/E' : Item(status=' M', 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), 'B' : Item(status=' ', wc_rev=8), 'mu' : Item(status=' ', wc_rev=8), 'C' : Item(status=' ', wc_rev=8), }) expected_disk = wc.State('', { '' : Item(props={SVN_PROP_MERGEINFO : '/A:5-8'}), 'D/H/omega' : Item("New content", props={SVN_PROP_MERGEINFO : '/A/D/H/omega:5-8'}), 'D/H/chi' : Item("This is the file 'chi'.\n"), 'D/H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5-8*'}), 'D/gamma' : Item("This is the file 'gamma'.\n"), 'D' : Item(props={SVN_PROP_MERGEINFO : '/A/D:5-8*'}), 'B/lambda' : Item("This is the file 'lambda'.\n"), 'B/E' : Item(props={SVN_PROP_MERGEINFO : ''}), 'B/E/alpha' : Item("This is the file 'alpha'.\n"), 'B/E/beta' : Item("This is the file 'beta'.\n"), 'B/F' : Item(), 'B' : Item(), 'mu' : Item("This is the file 'mu'.\n"), 'C' : Item(), }) expected_skip = wc.State(A_COPY_2_path, { 'B/E' : Item(verb='Skipped missing target'), 'D/G' : Item(verb='Skipped missing target'), 'D/H/psi' : Item(verb='Skipped missing target'), }) svntest.actions.run_and_verify_merge(A_COPY_2_path, '4', '8', sbox.repo_url + '/A', None, expected_output, expected_mergeinfo_output, expected_elision_output, expected_disk, expected_status, expected_skip, check_props=True) # Merge r5:7 into the restricted WC's A_COPY_3. # # Again A_COPY_3/B/E should be skipped, but because we can't access the # source *or* the destination we expect its parent A_COPY_3/B to get # non-inheritable mergeinfo. A_COPY_3B's two existing siblings, # A_COPY_3/B/F and A_COPY_3/B/lambda are untouched by the merge so # neither gets any mergeinfo recorded. expected_output = wc.State(A_COPY_3_path, { 'D/G/rho' : Item(status='U '), }) expected_mergeinfo_output = wc.State(A_COPY_3_path, { '' : Item(status=' U'), 'B' : Item(status=' U'), }) expected_elision_output = wc.State(A_COPY_3_path, { }) expected_status = wc.State(A_COPY_3_path, { '' : Item(status=' M', 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), 'D/H' : Item(status=' ', wc_rev=8), 'D/gamma' : Item(status=' ', wc_rev=8), 'D' : 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='M ', wc_rev=8), 'D/G/tau' : Item(status=' ', wc_rev=8), 'B/lambda' : Item(status=' ', wc_rev=8), 'B/F' : Item(status=' ', wc_rev=8), 'B' : Item(status=' M', wc_rev=8), 'mu' : Item(status=' ', wc_rev=8), 'C' : Item(status=' ', wc_rev=8), }) expected_disk = wc.State('', { '' : Item(props={SVN_PROP_MERGEINFO : '/A:6-7'}), 'D/H/omega' : Item("This is the file 'omega'.\n"), 'D/H/chi' : Item("This is the file 'chi'.\n"), 'D/H/psi' : Item("This is the file 'psi'.\n"), 'D/H' : Item(), 'D/gamma' : Item("This is the file 'gamma'.\n"), '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"), 'B/lambda' : Item("This is the file 'lambda'.\n"), 'B/F' : Item(), 'B' : Item(props={SVN_PROP_MERGEINFO : '/A/B:6-7*'}), 'mu' : Item("This is the file 'mu'.\n"), 'C' : Item(), }) expected_skip = wc.State(A_COPY_3_path, {'B/E' : Item(verb='Skipped missing target')}) svntest.actions.run_and_verify_merge(A_COPY_3_path, '5', '7', sbox.repo_url + '/A', None, expected_output, expected_mergeinfo_output, expected_elision_output, expected_disk, expected_status, expected_skip, check_props=True) svntest.actions.run_and_verify_svn(None, [], 'revert', '--recursive', wc_restricted) # Test issue #2997. If a merge requires two separate editor drives and the # first is non-operative we should still update the mergeinfo to reflect # this. # # Merge -c5 -c8 to the restricted WC's A_COPY_2/D/H. r5 gets merged first # but is a no-op, r8 get's merged next and is operative so the mergeinfo # should be updated on the merge target to reflect both merges. expected_output = wc.State(A_COPY_2_H_path, { 'omega' : Item(status='U '), }) expected_elision_output = wc.State(A_COPY_2_H_path, { }) expected_status = wc.State(A_COPY_2_H_path, { '' : Item(status=' M', wc_rev=8), 'chi' : Item(status=' ', wc_rev=8), 'omega' : Item(status='MM', wc_rev=8), }) expected_disk = wc.State('', { '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5*,8*'}), 'omega' : Item("New content", props={SVN_PROP_MERGEINFO : '/A/D/H/omega:8'}), 'chi' : Item("This is the file 'chi'.\n"), }) expected_skip = wc.State(A_COPY_2_H_path, { 'psi' : Item(verb='Skipped missing target'), }) # Note we don't bother checking expected mergeinfo output because the # multiple merges being performed here, -c5 and -c8, will result in # first ' U' and then ' G' mergeinfo notifications. Our expected # tree structures can't handle checking for multiple values for the # same key. svntest.actions.run_and_verify_merge(A_COPY_2_H_path, '4', '5', sbox.repo_url + '/A/D/H', None, expected_output, None, # expected_mergeinfo_output, expected_elision_output, expected_disk, expected_status, expected_skip, [], True, False, '-c5', '-c8', A_COPY_2_H_path) # Test issue #2829 'Improve handling for skipped paths encountered # during a merge' # Revert previous changes to restricted WC svntest.actions.run_and_verify_svn(None, [], 'revert', '--recursive', wc_restricted) # Add new path 'A/D/H/zeta' svntest.main.file_write(zeta_path, "This is the file 'zeta'.\n") svntest.actions.run_and_verify_svn(None, [], 'add', zeta_path) expected_output = wc.State(wc_dir, {'A/D/H/zeta' : Item(verb='Adding')}) wc_status.add({'A/D/H/zeta' : Item(status=' ', wc_rev=9)}) svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) # 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, check_props=True) # 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 # 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, [], 'revert', '--recursive', wc_restricted) 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'), 'zeta' : Item(status=' U'), }) expected_elision_output = wc.State(A_COPY_2_H_path, { }) expected_status = wc.State(A_COPY_2_H_path, { '' : Item(status=' M', wc_rev=8), 'chi' : Item(status=' ', wc_rev=8), 'omega' : Item(status='MM', wc_rev=8), 'zeta' : Item(status='A ', copied='+', wc_rev='-'), }) expected_disk = wc.State('', { '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5-9*'}), 'omega' : Item("New content", 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, { 'psi' : Item(verb='Skipped missing target'), }) 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, expected_elision_output, expected_disk, expected_status, expected_skip, check_props=True) @SkipUnless(server_has_mergeinfo) @Issue(2876) def merge_fails_if_subtree_is_deleted_on_src(sbox): "merge fails if subtree is deleted on src" ## See https://issues.apache.org/jira/browse/SVN-2876. ## # Create a WC sbox.build() wc_dir = sbox.wc_dir if is_ra_type_svn() or is_ra_type_dav(): write_authz_file(sbox, {"/" : "* = rw", "/unrelated" : ("* =\n" + svntest.main.wc_author2 + " = rw")}) # Some paths we'll care about Acopy_path = sbox.ospath('A_copy') gamma_path = sbox.ospath('A/D/gamma') Acopy_gamma_path = sbox.ospath('A_copy/D/gamma') Acopy_D_path = sbox.ospath('A_copy/D') A_url = sbox.repo_url + '/A' Acopy_url = sbox.repo_url + '/A_copy' # Contents to be added to 'gamma' new_content = "line1\nline2\nline3\nline4\nline5\n" svntest.main.file_write(gamma_path, new_content) # Create expected output tree for commit expected_output = wc.State(wc_dir, { 'A/D/gamma' : Item(verb='Sending'), }) # Create expected status tree for commit expected_status = svntest.actions.get_virginal_state(wc_dir, 1) expected_status.tweak('A/D/gamma', wc_rev=2) # Commit the new content svntest.actions.run_and_verify_commit(wc_dir, expected_output, expected_status) svntest.actions.run_and_verify_svn(None, [], 'cp', A_url, Acopy_url, '-m', 'create a new copy of A') # Update working copy svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) svntest.main.file_substitute(gamma_path, "line1", "this is line1") # Create expected output tree for commit expected_output = wc.State(wc_dir, { 'A/D/gamma' : Item(verb='Sending'), }) # Create expected status tree for commit expected_status.tweak(wc_rev=3) expected_status.tweak('A/D/gamma', wc_rev=4) expected_status.add({ 'A_copy' : Item(status=' ', wc_rev=3), 'A_copy/B' : Item(status=' ', wc_rev=3), 'A_copy/B/lambda' : Item(status=' ', wc_rev=3), 'A_copy/B/E' : Item(status=' ', wc_rev=3), 'A_copy/B/E/alpha': Item(status=' ', wc_rev=3), 'A_copy/B/E/beta' : Item(status=' ', wc_rev=3), 'A_copy/B/F' : Item(status=' ', wc_rev=3), 'A_copy/mu' : Item(status=' ', wc_rev=3), 'A_copy/C' : Item(status=' ', wc_rev=3), 'A_copy/D' : Item(status=' ', wc_rev=3), 'A_copy/D/gamma' : Item(status=' ', wc_rev=3), 'A_copy/D/G' : Item(status=' ', wc_rev=3), 'A_copy/D/G/pi' : Item(status=' ', wc_rev=3), 'A_copy/D/G/rho' : Item(status=' ', wc_rev=3), 'A_copy/D/G/tau' : Item(status=' ', wc_rev=3), 'A_copy/D/H' : Item(status=' ', wc_rev=3), 'A_copy/D/H/chi' : Item(status=' ', wc_rev=3), 'A_copy/D/H/omega': Item(status=' ', wc_rev=3), 'A_copy/D/H/psi' : Item(status=' ', wc_rev=3), }) svntest.actions.run_and_verify_commit(wc_dir, expected_output, expected_status) # Delete A/D/gamma from working copy svntest.actions.run_and_verify_svn(None, [], 'delete', gamma_path) # Create expected output tree for commit expected_output = wc.State(wc_dir, { 'A/D/gamma' : Item(verb='Deleting'), }) expected_status.remove('A/D/gamma') svntest.actions.run_and_verify_commit(wc_dir, expected_output, expected_status, [], wc_dir, wc_dir) svntest.actions.run_and_verify_svn( expected_merge_output([[3,4]], ['U ' + Acopy_gamma_path + '\n', ' U ' + Acopy_gamma_path + '\n']), [], 'merge', '-r1:4', A_url + '/D/gamma' + '@4', Acopy_gamma_path) # r6: create an empty (unreadable) commit. # Empty or unreadable revisions used to crash a svn 1.6+ client when # used with a 1.5 server: # http://svn.haxx.se/dev/archive-2009-04/0476.shtml svntest.main.run_svn(None, 'mkdir', sbox.repo_url + '/unrelated', '--username', svntest.main.wc_author2, '-m', 'creating a rev with no paths.') # A delete merged ontop of a modified file is normally a tree conflict, # see notes/tree-conflicts/detection.txt, but --force currently avoids # this. svntest.actions.run_and_verify_svn( expected_merge_output([[3,6]], ['D ' + Acopy_gamma_path + '\n', ' U ' + Acopy_path + '\n']), [], 'merge', '-r1:6', '--force', A_url, Acopy_path) @SkipUnless(svntest.main.server_has_mergeinfo) @Skip(svntest.main.is_ra_type_file) @Issue(3242) def reintegrate_fails_if_no_root_access(sbox): "reintegrate fails if no root access" # If a user is authorized to a reintegrate source and target, they # should be able to reintegrate, regardless of what authorization # they have to parents of the source and target. # # See https://issues.apache.org/jira/browse/SVN-3242#desc78 # Some paths we'll care about wc_dir = sbox.wc_dir A_path = sbox.ospath('A') A_COPY_path = sbox.ospath('A_COPY') beta_COPY_path = sbox.ospath('A_COPY/B/E/beta') 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') # Copy A@1 to A_COPY in r2, and then make some changes 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 WC. svntest.main.run_svn(None, 'up', wc_dir) # Sync A_COPY with A. expected_output = expected_merge_output([[2,7]], ['U ' + beta_COPY_path + '\n', 'U ' + rho_COPY_path + '\n', 'U ' + omega_COPY_path + '\n', 'U ' + psi_COPY_path + '\n', # Mergeinfo notification ' U ' + A_COPY_path + '\n']) svntest.actions.run_and_verify_svn(expected_output, [], 'merge', sbox.repo_url + '/A', A_COPY_path) sbox.simple_commit(message='synch A_COPY with A') # Update so we are ready for reintegrate. svntest.main.run_svn(None, 'up', wc_dir) # Change authz file so everybody has access to everything but the root. if is_ra_type_svn() or is_ra_type_dav(): write_restrictive_svnserve_conf(sbox.repo_dir) write_authz_file(sbox, {"/" : "* =", "/A" : "* = rw", "/A_COPY" : "* = rw", "/iota" : "* = rw"}) # Now reintegrate A_COPY back to A. The lack of access to the root of the # repository shouldn't be a problem. 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_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/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_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), }) expected_skip = wc.State(A_path, {}) 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_disk, expected_status, expected_skip, [], True, True, '--reintegrate', A_path) def diff_unauth_parent(sbox): "diff directory without reading parent" sbox.build(create_wc=False) # Create r2: Change A a bit svntest.actions.run_and_verify_svnmucc(None, [], 'propset', 'k', 'v', sbox.repo_url + '/A', '-m', 'set prop') # Create r3 Mark E and G svntest.actions.run_and_verify_svnmucc(None, [], 'propset', 'this-is', 'E', sbox.repo_url + '/A/B/E', 'propset', 'this-is', 'G', sbox.repo_url + '/A/D/G', '-m', 'set prop') # Create r4: Replace A/B/E with A/D/G svntest.actions.run_and_verify_svnmucc(None, [], 'rm', sbox.repo_url + '/A/B/E', 'cp', '3', sbox.repo_url + '/A/D/G', sbox.repo_url + '/A/B/E', '-m', 'replace A/B/E') if is_ra_type_svn() or is_ra_type_dav(): write_restrictive_svnserve_conf(sbox.repo_dir) write_authz_file(sbox, {"/" : "* =", "/A" : "* = rw"}) # Diff the property change expected_output = [ 'Index: .\n', '===================================================================\n', '--- .\t(revision 1)\n', '+++ .\t(revision 2)\n', '\n', 'Property changes on: .\n', '___________________________________________________________________\n', 'Added: k\n', '## -0,0 +1 ##\n', '+v\n', '\ No newline at end of property\n' ] svntest.actions.run_and_verify_svn(expected_output, [], 'diff', sbox.repo_url + '/A', '-c', '2') if is_ra_type_svn() or is_ra_type_dav(): write_authz_file(sbox, {"/" : "* =", "/A/B/E" : "* = rw"}) # Diff the replacement expected_output = [ 'Index: alpha\n', '===================================================================\n', '--- alpha\t(revision 3)\n', '+++ alpha\t(nonexistent)\n', '@@ -1 +0,0 @@\n', '-This is the file \'alpha\'.\n', 'Index: beta\n', '===================================================================\n', '--- beta\t(revision 3)\n', '+++ beta\t(nonexistent)\n', '@@ -1 +0,0 @@\n', '-This is the file \'beta\'.\n', 'Index: tau\n', '===================================================================\n', '--- tau\t(nonexistent)\n', '+++ tau\t(revision 4)\n', '@@ -0,0 +1 @@\n', '+This is the file \'tau\'.\n', 'Index: rho\n', '===================================================================\n', '--- rho\t(nonexistent)\n', '+++ rho\t(revision 4)\n', '@@ -0,0 +1 @@\n', '+This is the file \'rho\'.\n', 'Index: pi\n', '===================================================================\n', '--- pi\t(nonexistent)\n', '+++ pi\t(revision 4)\n', '@@ -0,0 +1 @@\n', '+This is the file \'pi\'.\n', ] if is_ra_type_svn() or is_ra_type_dav(): # Because we can't anchor above C we see just a changed C, not a # replacement expected_output += [ 'Index: .\n', '===================================================================\n', '--- .\t(revision 3)\n', '+++ .\t(revision 4)\n', '\n', 'Property changes on: .\n', '___________________________________________________________________\n', 'Modified: this-is\n', '## -1 +1 ##\n', '-E\n', '\ No newline at end of property\n', '+G\n', '\ No newline at end of property\n', ] else: # ### We should also see a property deletion here! expected_output += [ 'Index: .\n', '===================================================================\n', '--- .\t(revision 3)\n', '+++ .\t(nonexistent)\n', '\n', 'Property changes on: .\n', '___________________________________________________________________\n', 'Deleted: this-is\n', '## -1 +0,0 ##\n', '-E\n', '\ No newline at end of property\n', 'Index: .\n', '===================================================================\n', '--- .\t(nonexistent)\n', '+++ .\t(revision 4)\n', '\n', 'Property changes on: .\n', '___________________________________________________________________\n', 'Added: this-is\n', '## -0,0 +1 ##\n', '+G\n', '\ No newline at end of property\n', ] # Use two url diff, because 'svn diff url -c' uses copyfrom to diff against expected_output = svntest.verify.UnorderedOutput(expected_output) svntest.actions.run_and_verify_svn(expected_output, [], 'diff', sbox.repo_url + '/A/B/E@3', sbox.repo_url + '/A/B/E@4', '--notice-ancestry') # Do the same thing with summarize to really see directory deletes and adds if is_ra_type_svn() or is_ra_type_dav(): # With no rights on the parent directory we just see a property change on E expected_output = [ 'D %s/A/B/E/alpha\n' % sbox.repo_url, 'D %s/A/B/E/beta\n' % sbox.repo_url, 'A %s/A/B/E/tau\n' % sbox.repo_url, 'A %s/A/B/E/rho\n' % sbox.repo_url, 'A %s/A/B/E/pi\n' % sbox.repo_url, ' M %s/A/B/E\n' % sbox.repo_url, ] else: # But with rights on the parent we see a replacement of E expected_output = [ 'D %s/A/B/E/alpha\n' % sbox.repo_url, 'D %s/A/B/E/beta\n' % sbox.repo_url, 'D %s/A/B/E\n' % sbox.repo_url, 'A %s/A/B/E/tau\n' % sbox.repo_url, 'A %s/A/B/E/rho\n' % sbox.repo_url, 'A %s/A/B/E/pi\n' % sbox.repo_url, 'A %s/A/B/E\n' % sbox.repo_url, ] expected_output = svntest.verify.UnorderedOutput(expected_output) svntest.actions.run_and_verify_svn(expected_output, [], 'diff', sbox.repo_url + '/A/B/E@3', sbox.repo_url + '/A/B/E@4', '--notice-ancestry', '--summarize') ######################################################################## # Run the tests # list all tests here, starting with None: test_list = [ None, mergeinfo_and_skipped_paths, merge_fails_if_subtree_is_deleted_on_src, reintegrate_fails_if_no_root_access, diff_unauth_parent, ] serial_only = True if __name__ == '__main__': svntest.main.run_tests(test_list, serial_only = serial_only) # NOTREACHED ### End of file.