| #!/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. |