blob: b26e9c766716a4615d9391b131d45dd13d020146 [file] [log] [blame]
#!/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.