1#!/usr/bin/env python 2# 3# merge_authz_tests.py: merge tests that need to write an authz file 4# 5# Subversion is a tool for revision control. 6# See http://subversion.apache.org for more information. 7# 8# ==================================================================== 9# Licensed to the Apache Software Foundation (ASF) under one 10# or more contributor license agreements. See the NOTICE file 11# distributed with this work for additional information 12# regarding copyright ownership. The ASF licenses this file 13# to you under the Apache License, Version 2.0 (the 14# "License"); you may not use this file except in compliance 15# with the License. You may obtain a copy of the License at 16# 17# http://www.apache.org/licenses/LICENSE-2.0 18# 19# Unless required by applicable law or agreed to in writing, 20# software distributed under the License is distributed on an 21# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 22# KIND, either express or implied. See the License for the 23# specific language governing permissions and limitations 24# under the License. 25###################################################################### 26 27# General modules 28import shutil, sys, re, os 29import time 30 31# Our testing module 32import svntest 33from svntest import wc 34 35# (abbreviation) 36Item = wc.StateItem 37Skip = svntest.testcase.Skip_deco 38SkipUnless = svntest.testcase.SkipUnless_deco 39XFail = svntest.testcase.XFail_deco 40Issues = svntest.testcase.Issues_deco 41Issue = svntest.testcase.Issue_deco 42Wimp = svntest.testcase.Wimp_deco 43 44from svntest.mergetrees import set_up_branch 45from svntest.mergetrees import expected_merge_output 46from svntest.main import SVN_PROP_MERGEINFO 47from svntest.main import write_restrictive_svnserve_conf 48from svntest.main import write_authz_file 49from svntest.main import is_ra_type_dav 50from svntest.main import is_ra_type_svn 51from svntest.main import server_has_mergeinfo 52from svntest.actions import fill_file_with_lines 53from svntest.actions import make_conflict_marker_text 54from svntest.actions import inject_conflict_into_expected_state 55 56###################################################################### 57# Tests 58# 59# Each test must return on success or raise on failure. 60 61 62#---------------------------------------------------------------------- 63 64# Test for issues 65# 66# #2893 - Handle merge info for portions of a tree not checked out due 67# to insufficient authz. 68# 69# #2997 - If skipped paths come first in operative merge mergeinfo 70# is incomplete 71# 72# #2829 - Improve handling for skipped paths encountered during a merge. 73# This is *not* a full test of issue #2829, see also merge_tests.py, 74# search for "2829". This tests the problem where a merge adds a path 75# with a missing sibling and so needs its own explicit mergeinfo. 76# 77# #4056 - Don't record non-inheritable mergeinfo if missing subtrees are not 78# touched by the full-depth diff 79@Issues(2893,2997,2829,4056) 80@SkipUnless(svntest.main.server_has_mergeinfo) 81@Skip(svntest.main.is_ra_type_file) 82def mergeinfo_and_skipped_paths(sbox): 83 "skipped paths get overriding mergeinfo" 84 85 # Test that we override the mergeinfo for child paths which weren't 86 # actually merged because they were skipped. 87 # 88 # This test covers paths skipped because: 89 # 90 # 1) The source of a merge is inaccessible due to authz restrictions. 91 # 2) Destination of merge is inaccessible due to authz restrictions. 92 # 3) Source *and* destination of merge is inaccessible due to authz 93 # restrictions. 94 95 sbox.build() 96 wc_dir = sbox.wc_dir 97 wc_disk, wc_status = set_up_branch(sbox, False, 3) 98 99 # Create a restrictive authz where part of the merge source and part 100 # of the target are inaccesible. 101 write_restrictive_svnserve_conf(sbox.repo_dir) 102 write_authz_file(sbox, {"/" : svntest.main.wc_author +"=rw", 103 # Make a directory in the merge source inaccessible. 104 "/A/B/E" : svntest.main.wc_author + "=", 105 # Make a file and dir in the merge destination 106 # inaccessible. 107 "/A_COPY_2/D/H/psi" : svntest.main.wc_author + "=", 108 "/A_COPY_2/D/G" : svntest.main.wc_author + "=", 109 # Make the source and destination inaccessible. 110 "/A_COPY_3/B/E" : svntest.main.wc_author + "=", 111 }) 112 113 # Checkout just the branch under the newly restricted authz. 114 wc_restricted = sbox.add_wc_path('restricted') 115 svntest.actions.run_and_verify_svn(None, [], 'checkout', 116 sbox.repo_url, 117 wc_restricted) 118 119 # Some paths we'll use in the second WC. 120 A_COPY_path = os.path.join(wc_restricted, "A_COPY") 121 A_COPY_2_path = os.path.join(wc_restricted, "A_COPY_2") 122 A_COPY_2_H_path = os.path.join(wc_restricted, "A_COPY_2", "D", "H") 123 A_COPY_3_path = os.path.join(wc_restricted, "A_COPY_3") 124 omega_path = os.path.join(wc_restricted, "A_COPY", "D", "H", "omega") 125 zeta_path = sbox.ospath("A/D/H/zeta") 126 127 # Merge r4:8 into the restricted WC's A_COPY. 128 # 129 # We expect A_COPY/B/E to be skipped because we can't access the source 130 # and A_COPY/D/H/omega because it is missing. Since we have A_COPY/B/E 131 # we should override it's inherited mergeinfo, giving it just what it 132 # inherited from A_COPY before the merge. 133 expected_output = wc.State(A_COPY_path, { 134 'D/G/rho' : Item(status='U '), 135 'D/H/psi' : Item(status='U '), 136 'D/H/omega' : Item(status='U '), 137 }) 138 expected_mergeinfo_output = wc.State(A_COPY_path, { 139 '' : Item(status=' U'), 140 'B/E' : Item(status=' U'), 141 }) 142 expected_elision_output = wc.State(A_COPY_path, { 143 }) 144 expected_status = wc.State(A_COPY_path, { 145 '' : Item(status=' M', wc_rev=8), 146 'D/H/chi' : Item(status=' ', wc_rev=8), 147 'D/H/psi' : Item(status='M ', wc_rev=8), 148 'D/H/omega' : Item(status='M ', wc_rev=8), 149 'D/H' : Item(status=' ', wc_rev=8), 150 'D/G/pi' : Item(status=' ', wc_rev=8), 151 'D/G/rho' : Item(status='M ', wc_rev=8), 152 'D/G/tau' : Item(status=' ', wc_rev=8), 153 'D/G' : Item(status=' ', wc_rev=8), 154 'D/gamma' : Item(status=' ', wc_rev=8), 155 'D' : Item(status=' ', wc_rev=8), 156 'B/lambda' : Item(status=' ', wc_rev=8), 157 'B/E' : Item(status=' M', wc_rev=8), 158 'B/E/alpha' : Item(status=' ', wc_rev=8), 159 'B/E/beta' : Item(status=' ', wc_rev=8), 160 'B/F' : Item(status=' ', wc_rev=8), 161 'B' : Item(status=' ', wc_rev=8), 162 'mu' : Item(status=' ', wc_rev=8), 163 'C' : Item(status=' ', wc_rev=8), 164 }) 165 expected_disk = wc.State('', { 166 '' : Item(props={SVN_PROP_MERGEINFO : '/A:5-8'}), 167 'D/H/psi' : Item("New content"), 168 'D/H/chi' : Item("This is the file 'chi'.\n"), 169 'D/H/omega' : Item("New content"), 170 'D/H' : Item(), 171 'D/G/pi' : Item("This is the file 'pi'.\n"), 172 'D/G/rho' : Item("New content"), 173 'D/G/tau' : Item("This is the file 'tau'.\n"), 174 'D/G' : Item(), 175 'D/gamma' : Item("This is the file 'gamma'.\n"), 176 'D' : Item(), 177 'B/lambda' : Item("This is the file 'lambda'.\n"), 178 'B/E' : Item(props={SVN_PROP_MERGEINFO : ''}), 179 'B/E/alpha' : Item("This is the file 'alpha'.\n"), 180 'B/E/beta' : Item("This is the file 'beta'.\n"), 181 'B/F' : Item(), 182 'B' : Item(), 183 'mu' : Item("This is the file 'mu'.\n"), 184 'C' : Item(), 185 }) 186 expected_skip = wc.State(A_COPY_path, { 187 'B/E' : Item(verb='Skipped missing target'), 188 }) 189 svntest.actions.run_and_verify_merge(A_COPY_path, '4', '8', 190 sbox.repo_url + '/A', None, 191 expected_output, 192 expected_mergeinfo_output, 193 expected_elision_output, 194 expected_disk, 195 expected_status, 196 expected_skip, 197 check_props=True) 198 199 # Merge r4:8 into the restricted WC's A_COPY_2. 200 # 201 # As before we expect A_COPY_2/B/E to be skipped because we can't access the 202 # source but now the destination paths A_COPY_2/D/G, A_COPY_2/D/G/rho, and 203 # A_COPY_2/D/H/psi should also be skipped because our test user doesn't have 204 # access. 205 # 206 # After the merge the parents of the missing dest paths, A_COPY_2/D and 207 # A_COPY_2/D/H get non-inheritable mergeinfo. Those parents' children that 208 # *are* present and are affected by the merge, only A_COPY_2/D/H/omega in 209 # this case, get their own mergeinfo. Note that A_COPY_2/D/H is both the 210 # parent of a missing child and the sibling of missing child, but the former 211 # always takes precedence in terms of getting *non*-inheritable mergeinfo. 212 expected_output = wc.State(A_COPY_2_path, { 213 'D/H/omega' : Item(status='U '), 214 # Below the skip 215 'D/G/rho' : Item(status=' ', treeconflict='U'), 216 }) 217 expected_mergeinfo_output = wc.State(A_COPY_2_path, { 218 '' : Item(status=' U'), 219 'D' : Item(status=' U'), 220 'D/H' : Item(status=' U'), 221 'D/H/omega' : Item(status=' U'), 222 'B/E' : Item(status=' U'), 223 }) 224 expected_elision_output = wc.State(A_COPY_2_path, { 225 }) 226 expected_status = wc.State(A_COPY_2_path, { 227 '' : Item(status=' M', wc_rev=8), 228 'D/H/chi' : Item(status=' ', wc_rev=8), 229 'D/H/omega' : Item(status='MM', wc_rev=8), 230 'D/H' : Item(status=' M', wc_rev=8), 231 'D/gamma' : Item(status=' ', wc_rev=8), 232 'D' : Item(status=' M', wc_rev=8), 233 'B/lambda' : Item(status=' ', wc_rev=8), 234 'B/E' : Item(status=' M', wc_rev=8), 235 'B/E/alpha' : Item(status=' ', wc_rev=8), 236 'B/E/beta' : Item(status=' ', wc_rev=8), 237 'B/F' : Item(status=' ', wc_rev=8), 238 'B' : Item(status=' ', wc_rev=8), 239 'mu' : Item(status=' ', wc_rev=8), 240 'C' : Item(status=' ', wc_rev=8), 241 }) 242 expected_disk = wc.State('', { 243 '' : Item(props={SVN_PROP_MERGEINFO : '/A:5-8'}), 244 'D/H/omega' : Item("New content", 245 props={SVN_PROP_MERGEINFO : '/A/D/H/omega:5-8'}), 246 'D/H/chi' : Item("This is the file 'chi'.\n"), 247 'D/H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5-8*'}), 248 'D/gamma' : Item("This is the file 'gamma'.\n"), 249 'D' : Item(props={SVN_PROP_MERGEINFO : '/A/D:5-8*'}), 250 'B/lambda' : Item("This is the file 'lambda'.\n"), 251 'B/E' : Item(props={SVN_PROP_MERGEINFO : ''}), 252 'B/E/alpha' : Item("This is the file 'alpha'.\n"), 253 'B/E/beta' : Item("This is the file 'beta'.\n"), 254 'B/F' : Item(), 255 'B' : Item(), 256 'mu' : Item("This is the file 'mu'.\n"), 257 'C' : Item(), 258 }) 259 expected_skip = wc.State(A_COPY_2_path, { 260 'B/E' : Item(verb='Skipped missing target'), 261 'D/G' : Item(verb='Skipped missing target'), 262 'D/H/psi' : Item(verb='Skipped missing target'), 263 }) 264 svntest.actions.run_and_verify_merge(A_COPY_2_path, '4', '8', 265 sbox.repo_url + '/A', None, 266 expected_output, 267 expected_mergeinfo_output, 268 expected_elision_output, 269 expected_disk, 270 expected_status, 271 expected_skip, 272 check_props=True) 273 274 # Merge r5:7 into the restricted WC's A_COPY_3. 275 # 276 # Again A_COPY_3/B/E should be skipped, but because we can't access the 277 # source *or* the destination we expect its parent A_COPY_3/B to get 278 # non-inheritable mergeinfo. A_COPY_3B's two existing siblings, 279 # A_COPY_3/B/F and A_COPY_3/B/lambda are untouched by the merge so 280 # neither gets any mergeinfo recorded. 281 expected_output = wc.State(A_COPY_3_path, { 282 'D/G/rho' : Item(status='U '), 283 }) 284 expected_mergeinfo_output = wc.State(A_COPY_3_path, { 285 '' : Item(status=' U'), 286 'B' : Item(status=' U'), 287 }) 288 expected_elision_output = wc.State(A_COPY_3_path, { 289 }) 290 expected_status = wc.State(A_COPY_3_path, { 291 '' : Item(status=' M', wc_rev=8), 292 'D/H/chi' : Item(status=' ', wc_rev=8), 293 'D/H/omega' : Item(status=' ', wc_rev=8), 294 'D/H/psi' : Item(status=' ', wc_rev=8), 295 'D/H' : Item(status=' ', wc_rev=8), 296 'D/gamma' : Item(status=' ', wc_rev=8), 297 'D' : Item(status=' ', wc_rev=8), 298 'D/G' : Item(status=' ', wc_rev=8), 299 'D/G/pi' : Item(status=' ', wc_rev=8), 300 'D/G/rho' : Item(status='M ', wc_rev=8), 301 'D/G/tau' : Item(status=' ', wc_rev=8), 302 'B/lambda' : Item(status=' ', wc_rev=8), 303 'B/F' : Item(status=' ', wc_rev=8), 304 'B' : Item(status=' M', wc_rev=8), 305 'mu' : Item(status=' ', wc_rev=8), 306 'C' : Item(status=' ', wc_rev=8), 307 }) 308 expected_disk = wc.State('', { 309 '' : Item(props={SVN_PROP_MERGEINFO : '/A:6-7'}), 310 'D/H/omega' : Item("This is the file 'omega'.\n"), 311 'D/H/chi' : Item("This is the file 'chi'.\n"), 312 'D/H/psi' : Item("This is the file 'psi'.\n"), 313 'D/H' : Item(), 314 'D/gamma' : Item("This is the file 'gamma'.\n"), 315 'D' : Item(), 316 'D/G' : Item(), 317 'D/G/pi' : Item("This is the file 'pi'.\n"), 318 'D/G/rho' : Item("New content"), 319 'D/G/tau' : Item("This is the file 'tau'.\n"), 320 'B/lambda' : Item("This is the file 'lambda'.\n"), 321 'B/F' : Item(), 322 'B' : Item(props={SVN_PROP_MERGEINFO : '/A/B:6-7*'}), 323 'mu' : Item("This is the file 'mu'.\n"), 324 'C' : Item(), 325 }) 326 expected_skip = wc.State(A_COPY_3_path, 327 {'B/E' : Item(verb='Skipped missing target')}) 328 svntest.actions.run_and_verify_merge(A_COPY_3_path, '5', '7', 329 sbox.repo_url + '/A', None, 330 expected_output, 331 expected_mergeinfo_output, 332 expected_elision_output, 333 expected_disk, 334 expected_status, 335 expected_skip, 336 check_props=True) 337 svntest.actions.run_and_verify_svn(None, [], 'revert', '--recursive', 338 wc_restricted) 339 340 # Test issue #2997. If a merge requires two separate editor drives and the 341 # first is non-operative we should still update the mergeinfo to reflect 342 # this. 343 # 344 # Merge -c5 -c8 to the restricted WC's A_COPY_2/D/H. r5 gets merged first 345 # but is a no-op, r8 get's merged next and is operative so the mergeinfo 346 # should be updated on the merge target to reflect both merges. 347 expected_output = wc.State(A_COPY_2_H_path, { 348 'omega' : Item(status='U '), 349 }) 350 expected_elision_output = wc.State(A_COPY_2_H_path, { 351 }) 352 expected_status = wc.State(A_COPY_2_H_path, { 353 '' : Item(status=' M', wc_rev=8), 354 'chi' : Item(status=' ', wc_rev=8), 355 'omega' : Item(status='MM', wc_rev=8), 356 }) 357 expected_disk = wc.State('', { 358 '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5*,8*'}), 359 'omega' : Item("New content", 360 props={SVN_PROP_MERGEINFO : '/A/D/H/omega:8'}), 361 'chi' : Item("This is the file 'chi'.\n"), 362 }) 363 expected_skip = wc.State(A_COPY_2_H_path, { 364 'psi' : Item(verb='Skipped missing target'), 365 }) 366 # Note we don't bother checking expected mergeinfo output because the 367 # multiple merges being performed here, -c5 and -c8, will result in 368 # first ' U' and then ' G' mergeinfo notifications. Our expected 369 # tree structures can't handle checking for multiple values for the 370 # same key. 371 svntest.actions.run_and_verify_merge(A_COPY_2_H_path, '4', '5', 372 sbox.repo_url + '/A/D/H', None, 373 expected_output, 374 None, # expected_mergeinfo_output, 375 expected_elision_output, 376 expected_disk, 377 expected_status, 378 expected_skip, 379 [], True, False, 380 '-c5', '-c8', 381 A_COPY_2_H_path) 382 383 # Test issue #2829 'Improve handling for skipped paths encountered 384 # during a merge' 385 386 # Revert previous changes to restricted WC 387 svntest.actions.run_and_verify_svn(None, [], 'revert', '--recursive', 388 wc_restricted) 389 # Add new path 'A/D/H/zeta' 390 svntest.main.file_write(zeta_path, "This is the file 'zeta'.\n") 391 svntest.actions.run_and_verify_svn(None, [], 'add', zeta_path) 392 expected_output = wc.State(wc_dir, {'A/D/H/zeta' : Item(verb='Adding')}) 393 wc_status.add({'A/D/H/zeta' : Item(status=' ', wc_rev=9)}) 394 svntest.actions.run_and_verify_commit(wc_dir, expected_output, 395 wc_status) 396 397 # Merge -r7:9 to the restricted WC's A_COPY_2/D/H. 398 # 399 # r9 adds a path, 'A_COPY_2/D/H/zeta', which has a missing sibling 'psi', 400 # but since 'psi' is untouched by the merge it isn't skipped, and since it 401 # isn't skipped, its parent 'A_COPY_2/D/H' won't get non-inheritable 402 # mergeinfo set on it to describe the merge, so none of the parent's 403 # children will get explicit mergeinfo -- see issue #4056. 404 expected_output = wc.State(A_COPY_2_H_path, { 405 'omega' : Item(status='U '), 406 'zeta' : Item(status='A '), 407 }) 408 expected_mergeinfo_output = wc.State(A_COPY_2_H_path, { 409 '' : Item(status=' U'), 410 'omega' : Item(status=' U'), 411 }) 412 expected_elision_output = wc.State(A_COPY_2_H_path, { 413 'omega' : Item(status=' U'), 414 }) 415 expected_status = wc.State(A_COPY_2_H_path, { 416 '' : Item(status=' M', wc_rev=8), 417 'chi' : Item(status=' ', wc_rev=8), 418 'omega' : Item(status='M ', wc_rev=8), 419 'zeta' : Item(status='A ', copied='+', wc_rev='-'), 420 }) 421 expected_disk = wc.State('', { 422 '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:8-9'}), 423 'omega' : Item("New content"), 424 'chi' : Item("This is the file 'chi'.\n"), 425 'zeta' : Item("This is the file 'zeta'.\n"), 426 }) 427 expected_skip = wc.State(A_COPY_2_H_path, {}) 428 svntest.actions.run_and_verify_merge(A_COPY_2_H_path, '7', '9', 429 sbox.repo_url + '/A/D/H', None, 430 expected_output, 431 expected_mergeinfo_output, 432 expected_elision_output, 433 expected_disk, 434 expected_status, 435 expected_skip, 436 check_props=True) 437 438 # Merge -r4:9 to the restricted WC's A_COPY_2/D/H. 439 # 440 # r9 adds a path, 'A_COPY_2/D/H/zeta', which has a parent with 441 # non-inheritable mergeinfo (due to the fact 'A_COPY_2/D/H/psi' is missing 442 # and skipped). 'A_COPY_2/D/H/zeta' must therefore get its own explicit 443 # mergeinfo from this merge. 444 svntest.actions.run_and_verify_svn(None, [], 'revert', '--recursive', 445 wc_restricted) 446 expected_output = wc.State(A_COPY_2_H_path, { 447 'omega' : Item(status='U '), 448 'zeta' : Item(status='A '), 449 }) 450 expected_mergeinfo_output = wc.State(A_COPY_2_H_path, { 451 '' : Item(status=' U'), 452 'omega' : Item(status=' U'), 453 'zeta' : Item(status=' U'), 454 }) 455 expected_elision_output = wc.State(A_COPY_2_H_path, { 456 }) 457 expected_status = wc.State(A_COPY_2_H_path, { 458 '' : Item(status=' M', wc_rev=8), 459 'chi' : Item(status=' ', wc_rev=8), 460 'omega' : Item(status='MM', wc_rev=8), 461 'zeta' : Item(status='A ', copied='+', wc_rev='-'), 462 }) 463 expected_disk = wc.State('', { 464 '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5-9*'}), 465 'omega' : Item("New content", 466 props={SVN_PROP_MERGEINFO : '/A/D/H/omega:5-9'}), 467 'chi' : Item("This is the file 'chi'.\n"), 468 'zeta' : Item("This is the file 'zeta'.\n", 469 props={SVN_PROP_MERGEINFO : '/A/D/H/zeta:9'}), 470 }) 471 expected_skip = wc.State(A_COPY_2_H_path, { 472 'psi' : Item(verb='Skipped missing target'), 473 }) 474 svntest.actions.run_and_verify_merge(A_COPY_2_H_path, '4', '9', 475 sbox.repo_url + '/A/D/H', None, 476 expected_output, 477 expected_mergeinfo_output, 478 expected_elision_output, 479 expected_disk, 480 expected_status, 481 expected_skip, 482 check_props=True) 483 484@SkipUnless(server_has_mergeinfo) 485@Issue(2876) 486def merge_fails_if_subtree_is_deleted_on_src(sbox): 487 "merge fails if subtree is deleted on src" 488 489 ## See https://issues.apache.org/jira/browse/SVN-2876. ## 490 491 # Create a WC 492 sbox.build() 493 wc_dir = sbox.wc_dir 494 495 if is_ra_type_svn() or is_ra_type_dav(): 496 write_authz_file(sbox, {"/" : "* = rw", 497 "/unrelated" : ("* =\n" + 498 svntest.main.wc_author2 + " = rw")}) 499 500 # Some paths we'll care about 501 Acopy_path = sbox.ospath('A_copy') 502 gamma_path = sbox.ospath('A/D/gamma') 503 Acopy_gamma_path = sbox.ospath('A_copy/D/gamma') 504 Acopy_D_path = sbox.ospath('A_copy/D') 505 A_url = sbox.repo_url + '/A' 506 Acopy_url = sbox.repo_url + '/A_copy' 507 508 # Contents to be added to 'gamma' 509 new_content = "line1\nline2\nline3\nline4\nline5\n" 510 511 svntest.main.file_write(gamma_path, new_content) 512 513 # Create expected output tree for commit 514 expected_output = wc.State(wc_dir, { 515 'A/D/gamma' : Item(verb='Sending'), 516 }) 517 518 # Create expected status tree for commit 519 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 520 expected_status.tweak('A/D/gamma', wc_rev=2) 521 522 # Commit the new content 523 svntest.actions.run_and_verify_commit(wc_dir, expected_output, 524 expected_status) 525 526 svntest.actions.run_and_verify_svn(None, [], 'cp', A_url, Acopy_url, 527 '-m', 'create a new copy of A') 528 529 # Update working copy 530 svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) 531 532 svntest.main.file_substitute(gamma_path, "line1", "this is line1") 533 # Create expected output tree for commit 534 expected_output = wc.State(wc_dir, { 535 'A/D/gamma' : Item(verb='Sending'), 536 }) 537 538 # Create expected status tree for commit 539 expected_status.tweak(wc_rev=3) 540 expected_status.tweak('A/D/gamma', wc_rev=4) 541 expected_status.add({ 542 'A_copy' : Item(status=' ', wc_rev=3), 543 'A_copy/B' : Item(status=' ', wc_rev=3), 544 'A_copy/B/lambda' : Item(status=' ', wc_rev=3), 545 'A_copy/B/E' : Item(status=' ', wc_rev=3), 546 'A_copy/B/E/alpha': Item(status=' ', wc_rev=3), 547 'A_copy/B/E/beta' : Item(status=' ', wc_rev=3), 548 'A_copy/B/F' : Item(status=' ', wc_rev=3), 549 'A_copy/mu' : Item(status=' ', wc_rev=3), 550 'A_copy/C' : Item(status=' ', wc_rev=3), 551 'A_copy/D' : Item(status=' ', wc_rev=3), 552 'A_copy/D/gamma' : Item(status=' ', wc_rev=3), 553 'A_copy/D/G' : Item(status=' ', wc_rev=3), 554 'A_copy/D/G/pi' : Item(status=' ', wc_rev=3), 555 'A_copy/D/G/rho' : Item(status=' ', wc_rev=3), 556 'A_copy/D/G/tau' : Item(status=' ', wc_rev=3), 557 'A_copy/D/H' : Item(status=' ', wc_rev=3), 558 'A_copy/D/H/chi' : Item(status=' ', wc_rev=3), 559 'A_copy/D/H/omega': Item(status=' ', wc_rev=3), 560 'A_copy/D/H/psi' : Item(status=' ', wc_rev=3), 561 }) 562 563 svntest.actions.run_and_verify_commit(wc_dir, expected_output, 564 expected_status) 565 566 # Delete A/D/gamma from working copy 567 svntest.actions.run_and_verify_svn(None, [], 'delete', gamma_path) 568 # Create expected output tree for commit 569 expected_output = wc.State(wc_dir, { 570 'A/D/gamma' : Item(verb='Deleting'), 571 }) 572 573 expected_status.remove('A/D/gamma') 574 575 svntest.actions.run_and_verify_commit(wc_dir, 576 expected_output, 577 expected_status, 578 [], 579 wc_dir, wc_dir) 580 svntest.actions.run_and_verify_svn( 581 expected_merge_output([[3,4]], 582 ['U ' + Acopy_gamma_path + '\n', 583 ' U ' + Acopy_gamma_path + '\n']), 584 [], 'merge', '-r1:4', 585 A_url + '/D/gamma' + '@4', 586 Acopy_gamma_path) 587 588 # r6: create an empty (unreadable) commit. 589 # Empty or unreadable revisions used to crash a svn 1.6+ client when 590 # used with a 1.5 server: 591 # http://svn.haxx.se/dev/archive-2009-04/0476.shtml 592 svntest.main.run_svn(None, 'mkdir', sbox.repo_url + '/unrelated', 593 '--username', svntest.main.wc_author2, 594 '-m', 'creating a rev with no paths.') 595 596 # A delete merged ontop of a modified file is normally a tree conflict, 597 # see notes/tree-conflicts/detection.txt, but --force currently avoids 598 # this. 599 svntest.actions.run_and_verify_svn( 600 expected_merge_output([[3,6]], 601 ['D ' + Acopy_gamma_path + '\n', 602 ' U ' + Acopy_path + '\n']), 603 [], 'merge', '-r1:6', '--force', 604 A_url, Acopy_path) 605 606@SkipUnless(svntest.main.server_has_mergeinfo) 607@Skip(svntest.main.is_ra_type_file) 608@Issue(3242) 609def reintegrate_fails_if_no_root_access(sbox): 610 "reintegrate fails if no root access" 611 612 # If a user is authorized to a reintegrate source and target, they 613 # should be able to reintegrate, regardless of what authorization 614 # they have to parents of the source and target. 615 # 616 # See https://issues.apache.org/jira/browse/SVN-3242#desc78 617 618 # Some paths we'll care about 619 wc_dir = sbox.wc_dir 620 A_path = sbox.ospath('A') 621 A_COPY_path = sbox.ospath('A_COPY') 622 beta_COPY_path = sbox.ospath('A_COPY/B/E/beta') 623 rho_COPY_path = sbox.ospath('A_COPY/D/G/rho') 624 omega_COPY_path = sbox.ospath('A_COPY/D/H/omega') 625 psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') 626 627 # Copy A@1 to A_COPY in r2, and then make some changes to A in r3-6. 628 sbox.build() 629 wc_dir = sbox.wc_dir 630 expected_disk, expected_status = set_up_branch(sbox) 631 632 # Make a change on the branch, to A_COPY/mu, commit in r7. 633 svntest.main.file_write(sbox.ospath("A_COPY/mu"), 634 "Changed on the branch.") 635 expected_output = wc.State(wc_dir, {'A_COPY/mu' : Item(verb='Sending')}) 636 expected_status.tweak('A_COPY/mu', wc_rev=7) 637 svntest.actions.run_and_verify_commit(wc_dir, expected_output, 638 expected_status) 639 expected_disk.tweak('A_COPY/mu', contents='Changed on the branch.') 640 641 # Update the WC. 642 svntest.main.run_svn(None, 'up', wc_dir) 643 644 645 # Sync A_COPY with A. 646 expected_output = expected_merge_output([[2,7]], 647 ['U ' + beta_COPY_path + '\n', 648 'U ' + rho_COPY_path + '\n', 649 'U ' + omega_COPY_path + '\n', 650 'U ' + psi_COPY_path + '\n', 651 # Mergeinfo notification 652 ' U ' + A_COPY_path + '\n']) 653 svntest.actions.run_and_verify_svn(expected_output, [], 'merge', 654 sbox.repo_url + '/A', A_COPY_path) 655 sbox.simple_commit(message='synch A_COPY with A') 656 657 # Update so we are ready for reintegrate. 658 svntest.main.run_svn(None, 'up', wc_dir) 659 660 # Change authz file so everybody has access to everything but the root. 661 if is_ra_type_svn() or is_ra_type_dav(): 662 write_restrictive_svnserve_conf(sbox.repo_dir) 663 write_authz_file(sbox, {"/" : "* =", 664 "/A" : "* = rw", 665 "/A_COPY" : "* = rw", 666 "/iota" : "* = rw"}) 667 668 # Now reintegrate A_COPY back to A. The lack of access to the root of the 669 # repository shouldn't be a problem. 670 expected_output = wc.State(A_path, { 671 'mu' : Item(status='U '), 672 }) 673 expected_mergeinfo_output = wc.State(A_path, { 674 '' : Item(status=' U'), 675 }) 676 expected_elision_output = wc.State(A_path, { 677 }) 678 expected_disk = wc.State('', { 679 '' : Item(props={SVN_PROP_MERGEINFO : '/A_COPY:2-8'}), 680 'B' : Item(), 681 'B/lambda' : Item("This is the file 'lambda'.\n"), 682 'B/E' : Item(), 683 'B/E/alpha' : Item("This is the file 'alpha'.\n"), 684 'B/E/beta' : Item("New content"), 685 'B/F' : Item(), 686 'mu' : Item("Changed on the branch."), 687 'C' : Item(), 688 'D' : Item(), 689 'D/gamma' : Item("This is the file 'gamma'.\n"), 690 'D/G' : Item(), 691 'D/G/pi' : Item("This is the file 'pi'.\n"), 692 'D/G/rho' : Item("New content"), 693 'D/G/tau' : Item("This is the file 'tau'.\n"), 694 'D/H' : Item(), 695 'D/H/chi' : Item("This is the file 'chi'.\n"), 696 'D/H/omega' : Item("New content"), 697 'D/H/psi' : Item("New content"), 698 }) 699 expected_status = wc.State(A_path, { 700 "B" : Item(status=' ', wc_rev=8), 701 "B/lambda" : Item(status=' ', wc_rev=8), 702 "B/E" : Item(status=' ', wc_rev=8), 703 "B/E/alpha" : Item(status=' ', wc_rev=8), 704 "B/E/beta" : Item(status=' ', wc_rev=8), 705 "B/F" : Item(status=' ', wc_rev=8), 706 "mu" : Item(status='M ', wc_rev=8), 707 "C" : Item(status=' ', wc_rev=8), 708 "D" : Item(status=' ', wc_rev=8), 709 "D/gamma" : Item(status=' ', wc_rev=8), 710 "D/G" : Item(status=' ', wc_rev=8), 711 "D/G/pi" : Item(status=' ', wc_rev=8), 712 "D/G/rho" : Item(status=' ', wc_rev=8), 713 "D/G/tau" : Item(status=' ', wc_rev=8), 714 "D/H" : Item(status=' ', wc_rev=8), 715 "D/H/chi" : Item(status=' ', wc_rev=8), 716 "D/H/omega" : Item(status=' ', wc_rev=8), 717 "D/H/psi" : Item(status=' ', wc_rev=8), 718 "" : Item(status=' M', wc_rev=8), 719 }) 720 expected_skip = wc.State(A_path, {}) 721 svntest.actions.run_and_verify_merge(A_path, None, None, 722 sbox.repo_url + '/A_COPY', None, 723 expected_output, 724 expected_mergeinfo_output, 725 expected_elision_output, 726 expected_disk, 727 expected_status, 728 expected_skip, 729 [], True, True, 730 '--reintegrate', A_path) 731 732def diff_unauth_parent(sbox): 733 "diff directory without reading parent" 734 735 sbox.build(create_wc=False) 736 737 # Create r2: Change A a bit 738 svntest.actions.run_and_verify_svnmucc(None, [], 739 'propset', 'k', 'v', 740 sbox.repo_url + '/A', 741 '-m', 'set prop') 742 743 # Create r3 Mark E and G 744 svntest.actions.run_and_verify_svnmucc(None, [], 745 'propset', 'this-is', 'E', 746 sbox.repo_url + '/A/B/E', 747 'propset', 'this-is', 'G', 748 sbox.repo_url + '/A/D/G', 749 '-m', 'set prop') 750 751 # Create r4: Replace A/B/E with A/D/G 752 svntest.actions.run_and_verify_svnmucc(None, [], 753 'rm', sbox.repo_url + '/A/B/E', 754 'cp', '3', sbox.repo_url + '/A/D/G', 755 sbox.repo_url + '/A/B/E', 756 '-m', 'replace A/B/E') 757 758 759 if is_ra_type_svn() or is_ra_type_dav(): 760 write_restrictive_svnserve_conf(sbox.repo_dir) 761 write_authz_file(sbox, {"/" : "* =", 762 "/A" : "* = rw"}) 763 764 # Diff the property change 765 expected_output = [ 766 'Index: .\n', 767 '===================================================================\n', 768 '--- .\t(revision 1)\n', 769 '+++ .\t(revision 2)\n', 770 '\n', 771 'Property changes on: .\n', 772 '___________________________________________________________________\n', 773 'Added: k\n', 774 '## -0,0 +1 ##\n', 775 '+v\n', 776 '\ No newline at end of property\n' 777 ] 778 svntest.actions.run_and_verify_svn(expected_output, [], 779 'diff', sbox.repo_url + '/A', '-c', '2') 780 781 if is_ra_type_svn() or is_ra_type_dav(): 782 write_authz_file(sbox, {"/" : "* =", 783 "/A/B/E" : "* = rw"}) 784 785 # Diff the replacement 786 expected_output = [ 787 'Index: alpha\n', 788 '===================================================================\n', 789 '--- alpha\t(revision 3)\n', 790 '+++ alpha\t(nonexistent)\n', 791 '@@ -1 +0,0 @@\n', 792 '-This is the file \'alpha\'.\n', 793 'Index: beta\n', 794 '===================================================================\n', 795 '--- beta\t(revision 3)\n', 796 '+++ beta\t(nonexistent)\n', 797 '@@ -1 +0,0 @@\n', 798 '-This is the file \'beta\'.\n', 799 'Index: tau\n', 800 '===================================================================\n', 801 '--- tau\t(nonexistent)\n', 802 '+++ tau\t(revision 4)\n', 803 '@@ -0,0 +1 @@\n', 804 '+This is the file \'tau\'.\n', 805 'Index: rho\n', 806 '===================================================================\n', 807 '--- rho\t(nonexistent)\n', 808 '+++ rho\t(revision 4)\n', 809 '@@ -0,0 +1 @@\n', 810 '+This is the file \'rho\'.\n', 811 'Index: pi\n', 812 '===================================================================\n', 813 '--- pi\t(nonexistent)\n', 814 '+++ pi\t(revision 4)\n', 815 '@@ -0,0 +1 @@\n', 816 '+This is the file \'pi\'.\n', 817 ] 818 819 if is_ra_type_svn() or is_ra_type_dav(): 820 # Because we can't anchor above C we see just a changed C, not a 821 # replacement 822 expected_output += [ 823 'Index: .\n', 824 '===================================================================\n', 825 '--- .\t(revision 3)\n', 826 '+++ .\t(revision 4)\n', 827 '\n', 828 'Property changes on: .\n', 829 '___________________________________________________________________\n', 830 'Modified: this-is\n', 831 '## -1 +1 ##\n', 832 '-E\n', 833 '\ No newline at end of property\n', 834 '+G\n', 835 '\ No newline at end of property\n', 836 ] 837 else: 838 # ### We should also see a property deletion here! 839 expected_output += [ 840 'Index: .\n', 841 '===================================================================\n', 842 '--- .\t(revision 3)\n', 843 '+++ .\t(nonexistent)\n', 844 '\n', 845 'Property changes on: .\n', 846 '___________________________________________________________________\n', 847 'Deleted: this-is\n', 848 '## -1 +0,0 ##\n', 849 '-E\n', 850 '\ No newline at end of property\n', 851 'Index: .\n', 852 '===================================================================\n', 853 '--- .\t(nonexistent)\n', 854 '+++ .\t(revision 4)\n', 855 '\n', 856 'Property changes on: .\n', 857 '___________________________________________________________________\n', 858 'Added: this-is\n', 859 '## -0,0 +1 ##\n', 860 '+G\n', 861 '\ No newline at end of property\n', 862 ] 863 864 # Use two url diff, because 'svn diff url -c' uses copyfrom to diff against 865 expected_output = svntest.verify.UnorderedOutput(expected_output) 866 svntest.actions.run_and_verify_svn(expected_output, [], 867 'diff', sbox.repo_url + '/A/B/E@3', 868 sbox.repo_url + '/A/B/E@4', 869 '--notice-ancestry') 870 871 # Do the same thing with summarize to really see directory deletes and adds 872 if is_ra_type_svn() or is_ra_type_dav(): 873 # With no rights on the parent directory we just see a property change on E 874 expected_output = [ 875 'D %s/A/B/E/alpha\n' % sbox.repo_url, 876 'D %s/A/B/E/beta\n' % sbox.repo_url, 877 'A %s/A/B/E/tau\n' % sbox.repo_url, 878 'A %s/A/B/E/rho\n' % sbox.repo_url, 879 'A %s/A/B/E/pi\n' % sbox.repo_url, 880 ' M %s/A/B/E\n' % sbox.repo_url, 881 ] 882 else: 883 # But with rights on the parent we see a replacement of E 884 expected_output = [ 885 'D %s/A/B/E/alpha\n' % sbox.repo_url, 886 'D %s/A/B/E/beta\n' % sbox.repo_url, 887 'D %s/A/B/E\n' % sbox.repo_url, 888 'A %s/A/B/E/tau\n' % sbox.repo_url, 889 'A %s/A/B/E/rho\n' % sbox.repo_url, 890 'A %s/A/B/E/pi\n' % sbox.repo_url, 891 'A %s/A/B/E\n' % sbox.repo_url, 892 ] 893 894 expected_output = svntest.verify.UnorderedOutput(expected_output) 895 svntest.actions.run_and_verify_svn(expected_output, [], 896 'diff', sbox.repo_url + '/A/B/E@3', 897 sbox.repo_url + '/A/B/E@4', 898 '--notice-ancestry', '--summarize') 899 900######################################################################## 901# Run the tests 902 903 904# list all tests here, starting with None: 905test_list = [ None, 906 mergeinfo_and_skipped_paths, 907 merge_fails_if_subtree_is_deleted_on_src, 908 reintegrate_fails_if_no_root_access, 909 diff_unauth_parent, 910 ] 911serial_only = True 912 913if __name__ == '__main__': 914 svntest.main.run_tests(test_list, serial_only = serial_only) 915 # NOTREACHED 916 917 918### End of file. 919