The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env python
#
#  tree_conflict_tests.py:  testing tree-conflict cases.
#
#  Subversion is a tool for revision control.
#  See http://subversion.tigris.org for more information.
#
# ====================================================================
# Copyright (c) 2000-2009 CollabNet.  All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution.  The terms
# are also available at http://subversion.tigris.org/license-1.html.
# If newer versions of this license are posted there, you may use a
# newer version instead, at your option.
#
######################################################################

# General modules
import sys, re, os, traceback

# Our testing module
import svntest
from svntest import main,wc
from svntest.actions import run_and_verify_svn
from svntest.actions import run_and_verify_commit
from svntest.actions import run_and_verify_resolved

# (abbreviation)
Skip = svntest.testcase.Skip
SkipUnless = svntest.testcase.SkipUnless
XFail = svntest.testcase.XFail
Item = svntest.wc.StateItem
AnyOutput = svntest.verify.AnyOutput

# If verbose mode is enabled, print the LINE and a newline.
def verbose_print(line):
  if main.verbose_mode:
    print(line)

# If verbose mode is enabled, print the (assumed newline-terminated) LINES.
def verbose_printlines(lines):
  if main.verbose_mode:
    for line in lines:
      sys.stdout.write(line)

######################################################################
# Tests
#
#   Each test must return on success or raise on failure.


#----------------------------------------------------------------------

# The tests in this file are for cases where a tree conflict is to be raised.
# (They do not check that conflicts are not raised in other cases.)

# Note: Delete, Replace and Move are presently tested together but probably
# will eventually need to be tested separately.

# A tree conflict being raised means:
#   - the conflict is reported initially
#   - the conflict is persistently visible
#   - the conflict blocks commits until resolved
#   - the conflict blocks (some?) further merges
# Desired:
#   - interactive conflict resolution

# A "tree conflict on file P/F" means:
#   - the operation reports action code "C" on path P/F
#   - "svn status" reports status code "C" on path P/F
#   - "svn info" reports details of the conflict on path P/F
#   - "svn commit" fails if the user-requested targets include path P/F
#   - "svn merge/update/switch" fails if it tries to modify P/F in any way

# A "tree conflict on dir P/D" means:
#   - the operation reports action code "C" on path P/D
#   - "svn status" reports status code "C" on path P/D
#   - "svn info" reports details of the conflict on P/D
#   - "svn commit" fails if it includes any part of the P/D sub-tree
#   - "svn merge/up/sw" fails if it modifies any part of the P/D sub-tree

#----------------------------------------------------------------------

# Two sets of paths. The paths to be used for the destination of a copy
# or move must differ between the incoming change and the local mods,
# otherwise scenarios involving a move onto a move would conflict on the
# destination node as well as on the source, and we only want to be testing
# one thing at a time in most tests.
def incoming_paths(root_dir, parent_dir):
  """Create a set of paths in which the victims of tree conflicts are
     children of PARENT_DIR. ROOT_DIR should be a shallower directory
     in which items "F1" and "D1" can pre-exist and be shared across
     multiple parent dirs."""
  return {
    'F1' : os.path.join(root_dir,   "F1"),
    'F'  : os.path.join(parent_dir, "F"),
    'F2' : os.path.join(parent_dir, "F2-in"),
    'D1' : os.path.join(root_dir,   "D1"),
    'D'  : os.path.join(parent_dir, "D"),
    'D2' : os.path.join(parent_dir, "D2-in"),
  }
def localmod_paths(root_dir, parent_dir):
  """Create a set of paths in which the victims of tree conflicts are
     children of PARENT_DIR. ROOT_DIR should be a shallower directory
     in which items "F1" and "D1" can pre-exist and be shared across
     multiple parent dirs."""
  return {
    'F1' : os.path.join(root_dir,   "F1"),
    'F'  : os.path.join(parent_dir, "F"),
    'F2' : os.path.join(parent_dir, "F2-local"),
    'D1' : os.path.join(root_dir,   "D1"),
    'D'  : os.path.join(parent_dir, "D"),
    'D2' : os.path.join(parent_dir, "D2-local"),
  }

# Perform the action MODACTION on the WC items given by PATHS. The
# available actions can be seen within this function.
def modify(modaction, paths):
  F1 = paths['F1']  # existing file to copy from
  F  = paths['F']   # target file
  F2 = paths['F2']  # non-existing file to copy/move to
  D1 = paths['D1']  # existing dir to copy from
  D  = paths['D']   # target dir
  D2 = paths['D2']  # non-existing dir to copy/move to

  # print "  Mod: '" + modaction + "' '" + P + "'"

  if modaction == 'ft':    # file text-mod
    assert os.path.exists(F)
    main.file_append(F, "This is a text-mod of file F.\n")
  elif modaction == 'fP':  # file Prop-mod
    assert os.path.exists(F)
    main.run_svn(None, 'pset', 'fprop1', 'A prop set on file F.', F)
  elif modaction == 'dP':  # dir Prop-mod
    assert os.path.exists(D)
    main.run_svn(None, 'pset', 'dprop1', 'A prop set on dir D.', D)
  elif modaction == 'fD':  # file Delete
    assert os.path.exists(F)
    main.run_svn(None, 'del', F)
  elif modaction == 'dD':  # dir Delete
    assert os.path.exists(D)
    main.run_svn(None, 'del', D)
  elif modaction == 'fA':  # file Add (new)
    assert os.path.exists(F)
    main.run_svn(None, 'add', F)
    main.run_svn(None, 'pset', 'fprop2', 'A prop of added file F.', F)
  elif modaction == 'dA':  # dir Add (new)
    assert os.path.exists(D)
    main.run_svn(None, 'add', D)
    main.run_svn(None, 'pset', 'dprop2', 'A prop of added dir D.', D)
  elif modaction == 'fC':  # file Copy (from F1)
    main.run_svn(None, 'copy', F1, F)
  elif modaction == 'dC':  # dir Copy (from D1)
    main.run_svn(None, 'copy', D1, D)
  elif modaction == 'fM':  # file Move (to F2)
    main.run_svn(None, 'rename', F, F2)
  elif modaction == 'dM':  # dir Move (to D2)
    main.run_svn(None, 'rename', D, D2)
  elif modaction == 'fa':  # file add (new) on disk
    assert not os.path.exists(F)
    main.file_write(F, "This is file F.\n")
  elif modaction == 'da':  # dir add (new) on disk
    assert not os.path.exists(D)
    os.mkdir(D)
  elif modaction == 'fd':  # file delete from disk
    assert os.path.exists(F)
    os.remove(F)
  elif modaction == 'dd':  # dir delete from disk
    assert os.path.exists(D)
    os.remove(D)
  else:
    raise Exception("unknown modaction: '" + modaction + "'")

#----------------------------------------------------------------------

# Lists of change scenarios
#
# Each scenario expresses a change in terms of the client commands
# (including "move") that create that change. The change may exist in a
# repository, or may be applied to a WC by an "update" or "switch" or
# "merge", or may exist in a WC as a local modification.
#
# In addition, each scenario may include some local-modification actions
# that, if performed on the WC after this change, will make the disk state
# incompatible with the version-controlled state - e.g. by deleting a file
# that metadata says is present or vice-versa.

# File names:
#   F1 = any existing file
#   F  = the file-path being acted on
#   F2 = any non-existent file-path
#   D1 = any existing dir
#   D  = the dir-path being acted on
#   D2 = any non-existent dir-path
#   P  = the parent dir of F and of D

# Format of a change scenario:
# (
#   list of actions to create the file/directory to be changed later,
#   list of actions to make the change
# )

# Action lists to initialise the repository with a file or directory absent
# or present, to provide the starting point from which we perform the changes
# that are to be tested.
absent_f = []
absent_d = []
create_f = ['fa','fA']
create_d = ['da','dA']

# Scenarios that start with no existing versioned item
#
# CREATE:
# file-add(F) = add-new(F)        or copy(F1,F)(and modify?)
# dir-add(D)  = add-new(D)(deep?) or copy(D1,D)(and modify?)

f_adds = [
  #( absent_f, ['fa','fA'] ), ### local add-without-history: not a tree conflict
  ( absent_f, ['fC'] ),
  ( absent_f, ['fC','ft'] ), ### Fails because update seems to assume that the
                             ### local file is unmodified (same as issue 1736?).
  #( absent_f, ['fC','fP'] ),  # don't test all combinations, just because it's slow
]
d_adds = [
  #( absent_d, ['da','dA'] ), ### local add-without-history: not a tree conflict
  ( absent_d, ['dC'] ),
  #( absent_d, ['dC','dP'] ),  # not yet
]

# Scenarios that start with an existing versioned item
#
# GO-AWAY: node is no longer at the path where it was.
# file-del(F) = del(F) or move(F,F2)
# dir-del(D)  = del(D) or move(D,D2)
#
# REPLACE: node is no longer at the path where it was, but another node is.
# file-rpl(F) = file-del(F) + file-add(F)
# dir-rpl(D)  = dir-del(D) + dir-add(D)
# Note: Schedule replace-by-different-node-type is unsupported in WC.
#
# MODIFY:
# file-mod(F) = text-mod(F) and/or prop-mod(F)
# dir-mod(D)  = prop-mod(D) and/or file-mod(child-F) and/or dir-mod(child-D)

f_dels = [
  ( create_f, ['fD'] ),
  ( create_f, ['fM'] ),
]
d_dels = [
  ( create_d, ['dD'] ),
  ( create_d, ['dM'] ),
]

f_rpls = [
  #( create_f, ['fD','fa','fA'] ),  # replacement - not yet
  #( create_f, ['fM','fa','fC'] ),  # don't test all combinations, just because it's slow
]
d_rpls = [
  #( create_d, ['dD','dA'] ),  # replacement - not yet
  #( create_d, ['dM','dC'] ),  # don't test all combinations, just because it's slow
  # Note that directory replacement differs from file replacement: the
  # schedule-delete dir is still on disk and is re-used for the re-addition.
]
f_rpl_d = [
  # File replaced by directory: not yet testable
]
d_rpl_f = [
  # Directory replaced by file: not yet testable
]

f_mods = [
  ( create_f, ['ft'] ),
  ( create_f, ['fP'] ),
  #( create_f, ['ft','fP'] ),  # don't test all combinations, just because it's slow
]
d_mods = [
  ( create_d, ['dP'] ),
  # These test actions for operating on a child of the directory are not yet implemented:
  #( create_d, ['f_fA'] ),
  #( create_d, ['f_ft'] ),
  #( create_d, ['f_fP'] ),
  #( create_d, ['f_fD'] ),
  #( create_d, ['d_dP'] ),
  #( create_d, ['d_f_fA'] ),
]

#----------------------------------------------------------------------

# Set up all of the given SCENARIOS in their respective unique paths.
# This means committing their initialisation actions in r2, and then
# committing their change actions in r3 (assuming the repos was at r1).
# (See also the somewhat related svntest.actions.build_greek_tree_conflicts()
# and tree-conflicts tests using deep_trees in various other .py files.)
# SCENARIOS is a list of scenario tuples: (init_actions, change_actions).
# WC_DIR is a local path of an existing WC.
# BR_DIR is a nonexistent path within WC_DIR.
# BR_DIR and any necessary parent directories will be created, and then the
# scenario will be set up within it, and committed to the repository.
def set_up_repos(wc_dir, br_dir, scenarios):

  if not os.path.exists(br_dir):
    main.run_svn(None, "mkdir", "--parents", br_dir)

  # create the file F1 and dir D1 which the tests regard as pre-existing
  paths = incoming_paths(wc_dir, wc_dir)  # second arg is bogus but unimportant
  F1 = paths['F1']  # existing file to copy from
  main.file_write(F1, "This is initially file F1.\n")
  main.run_svn(None, 'add', F1)
  D1 = paths['D1']  # existing dir to copy from
  main.run_svn(None, 'mkdir', D1)

  # create the initial parent dirs, and each file or dir unless to-be-added
  for init_mods, action_mods in scenarios:
    path = "_".join(action_mods)
    P = os.path.join(br_dir, path)  # parent of items to be tested
    main.run_svn(None, 'mkdir', '--parents', P)
    for modaction in init_mods:
      modify(modaction, incoming_paths(wc_dir, P))
  run_and_verify_svn(None, AnyOutput, [],
                     'commit', '-m', 'Initial set-up.', wc_dir)
  # Capture the revision number
  init_rev = 2  ### hard-coded

  # modify all files and dirs in their various ways
  for _path, action_mods in scenarios:
    path = "_".join(action_mods)
    P = os.path.join(br_dir, path)  # parent
    for modaction in action_mods:
      modify(modaction, incoming_paths(wc_dir, P))

  # commit all the modifications
  run_and_verify_svn(None, AnyOutput, [],
                     'commit', '-m', 'Action.', wc_dir)
  # Capture the revision number
  changed_rev = 3  ### hard-coded

  return (init_rev, changed_rev)

#----------------------------------------------------------------------

# Apply each of the changes in INCOMING_SCENARIOS to each of the local
# modifications in LOCALMOD_SCENARIOS.
# Ensure that the result in each case includes a tree conflict on the parent.
# OPERATION = 'update' or 'switch' or 'merge'
# If COMMIT_LOCAL_MODS is true, the LOCALMOD_SCENARIOS will be committed to
# the target branch before applying the INCOMING_SCENARIOS.
def ensure_tree_conflict(sbox, operation,
                         incoming_scenarios, localmod_scenarios,
                         commit_local_mods):
  sbox.build()
  wc_dir = sbox.wc_dir

  def url_of(repo_relative_path):
    return sbox.repo_url + '/' + repo_relative_path

  verbose_print("")
  verbose_print("=== Starting a set of '" + operation + "' tests.")

  # Path to source branch, relative to wc_dir.
  # Source is where the "incoming" mods are made.
  source_br = "branch1"

  verbose_print("--- Creating changes in repos")
  source_wc_dir = os.path.join(wc_dir, source_br)
  source_left_rev, source_right_rev = set_up_repos(wc_dir, source_wc_dir,
                                                   incoming_scenarios)
  head_rev = source_right_rev  ### assumption

  # Local mods are the outer loop because cleaning up the WC is slow
  # ('svn revert' isn't sufficient because it leaves unversioned files)
  for _loc_init_mods, loc_action in localmod_scenarios:
    # Determine the branch (directory) in which local mods will be made.
    if operation == 'update':
      # Path to target branch (where conflicts are raised), relative to wc_dir.
      target_br = source_br
      target_start_rev = source_left_rev
    else:  # switch/merge
      # Make, and work in, a "branch2" that is a copy of "branch1".
      target_br = "branch2"
      run_and_verify_svn(None, AnyOutput, [],
                         'copy', '-r', str(source_left_rev), url_of(source_br),
                         url_of(target_br),
                         '-m', 'Create target branch.')
      head_rev += 1
      target_start_rev = head_rev

    main.run_svn(None, 'checkout', '-r', str(target_start_rev), sbox.repo_url,
                 wc_dir)

    saved_cwd = os.getcwd()
    os.chdir(wc_dir)

    for _inc_init_mods, inc_action in incoming_scenarios:
      scen_name = "_".join(inc_action)
      source_url = url_of(source_br + '/' + scen_name)
      target_path = os.path.join(target_br, scen_name)

      verbose_print("=== " + str(inc_action) + " onto " + str(loc_action))

      verbose_print("--- Making local mods")
      for modaction in loc_action:
        modify(modaction, localmod_paths(".", target_path))
      if commit_local_mods:
        run_and_verify_svn(None, AnyOutput, [],
                           'commit', target_path,
                           '-m', 'Mods in target branch.')
        head_rev += 1

      # What do we want to test for? (This selection could in future be
      # passed in to this function as a parameter.)
      test_what = [
        'commit-ood',
        'action',  # required for any of the following ones to work
        'notify',
        'commit-c',
        'status-c',
        'resolve',  # required for any of the following ones to work
        'status-nc',
        #'commit-ok',
        ]

      if 'commit-ood' in test_what:
        # For update, verify the pre-condition that WC is out of date.
        # For switch/merge, there is no such precondition.
        if operation == 'update':
          verbose_print("--- Trying to commit (expecting 'out-of-date' error)")
          run_and_verify_commit(".", None, None, "Commit failed",
                                target_path)

      if modaction.startswith('f'):
        victim = os.path.join(target_path, 'F')
      else:
        victim = os.path.join(target_path, 'D')

      # Perform the operation that tries to apply incoming changes to the WC.
      # The command is expected to do something (and give some output),
      # and it should raise a conflict but not an error.
      if 'action' in test_what:
        # Determine what notification to expect
        if 'notify' in test_what:
          expected_stdout = svntest.verify.ExpectedOutput("   C " + victim
                                                          + "\n",
                                                          match_all=False)
        else:
          expected_stdout = svntest.verify.AnyOutput
        # Do the main action
        if operation == 'update':
          verbose_print("--- Updating")
          run_and_verify_svn(None, expected_stdout, [],
                             'update', target_path)
        elif operation == 'switch':
          verbose_print("--- Switching")
          run_and_verify_svn(None, expected_stdout, [],
                             'switch', source_url, target_path)
        elif operation == 'merge':
          verbose_print("--- Merging")
          run_and_verify_svn(None, expected_stdout, [],
                             'merge', '--ignore-ancestry',
                             '-r', str(source_left_rev) + ':' + str(source_right_rev),
                             source_url, target_path)
        else:
          raise Exception("unknown operation: '" + operation + "'")

      if 'commit-c' in test_what:
        verbose_print("--- Trying to commit (expecting 'conflict' error)")
        ### run_and_verify_commit() requires an "output_tree" argument, but
        # here we get away with passing None because we know an implementation
        # detail: namely that it's not going to look at that argument if it
        # gets the stderr that we're expecting.
        run_and_verify_commit(".", None, None, ".*conflict.*", victim)

      if 'status-c' in test_what:
        verbose_print("--- Checking that 'status' reports the conflict")
        expected_stdout = svntest.verify.RegexOutput("^......C.* " +
                                                     re.escape(victim) + "$",
                                                     match_all=False)
        run_and_verify_svn(None, expected_stdout, [],
                           'status', victim)

      if 'resolve' in test_what:
        verbose_print("--- Resolving the conflict")
        # Make sure resolving the parent does nothing.
        run_and_verify_resolved([], os.path.dirname(victim))
        # The real resolved call.
        run_and_verify_resolved([victim])

      if 'status-nc' in test_what:
        verbose_print("--- Checking that 'status' does not report a conflict")
        exitcode, stdout, stderr = run_and_verify_svn(None, None, [],
                                                  'status', victim)
        for line in stdout:
          if line[6] == 'C': # and line.endswith(victim + '\n'):
            raise svntest.Failure("unexpected status C") # on path '" + victim + "'")

      if 'commit-ok' in test_what:
        verbose_print("--- Committing (should now succeed)")
        run_and_verify_svn(None, None, [],
                           'commit', '-m', '', target_path)
        target_start_rev += 1

      verbose_print("")

    os.chdir(saved_cwd)

    # Clean up the target branch and WC
    main.run_svn(None, 'revert', '-R', wc_dir)
    main.safe_rmtree(wc_dir)
    if operation != 'update':
      run_and_verify_svn(None, AnyOutput, [],
                         'delete', url_of(target_br),
                         '-m', 'Delete target branch.')
      head_rev += 1

#----------------------------------------------------------------------

# The main entry points for testing a set of scenarios.

# Test 'update' and/or 'switch'
# See test_wc_merge() for arguments.
def test_tc_up_sw(sbox, incoming_scen, wc_scen):
  sbox2 = sbox.clone_dependent()
  ensure_tree_conflict(sbox, 'update', incoming_scen, wc_scen, False)
  ensure_tree_conflict(sbox2, 'switch', incoming_scen, wc_scen, False)

# Test 'merge'
# INCOMING_SCEN is a list of scenarios describing the incoming changes to apply.
# BR_SCEN  is a list of scenarios describing how the local branch has
#   been modified relative to the merge-left source.
# WC_SCEN is a list of scenarios describing the local WC mods.
# One of BR_SCEN or WC_SCEN must be given but not both.
def test_tc_merge(sbox, incoming_scen, br_scen=None, wc_scen=None):
  if br_scen:
    ensure_tree_conflict(sbox, 'merge', incoming_scen, br_scen, True)
  else:
    ensure_tree_conflict(sbox, 'merge', incoming_scen, wc_scen, False)

#----------------------------------------------------------------------

# Tests for update/switch affecting a file, where the incoming change
# conflicts with a scheduled change in the WC.
#
# WC state: as scheduled (no obstruction)

def up_sw_file_mod_onto_del(sbox):
  "up/sw file: modify onto del/rpl/mv"
  test_tc_up_sw(sbox, f_mods, f_dels + f_rpls)
  # Note: See UC1 in notes/tree-conflicts/use-cases.txt.

def up_sw_file_del_onto_mod(sbox):
  "up/sw file: del/rpl/mv onto modify"
  # Results: tree-conflict on F
  #          no other change to WC (except possibly other half of move)
  #          ### OR (see Nico's email <>):
  #          schedule-delete but leave F on disk (can only apply with
  #            text-mod; prop-mod can't be preserved in this way)
  test_tc_up_sw(sbox, f_dels + f_rpls, f_mods)
  # Note: See UC2 in notes/tree-conflicts/use-cases.txt.

def up_sw_file_del_onto_del(sbox):
  "up/sw file: del/rpl/mv onto del/rpl/mv"
  test_tc_up_sw(sbox, f_dels + f_rpls, f_dels + f_rpls)
  # Note: See UC3 in notes/tree-conflicts/use-cases.txt.

def up_sw_file_add_onto_add(sbox):
  "up/sw file: add onto add"
  test_tc_up_sw(sbox, f_adds, f_adds)

#----------------------------------------------------------------------

# Tests for update/switch affecting a dir, where the incoming change
# conflicts with a scheduled change in the WC.

def up_sw_dir_mod_onto_del(sbox):
  "up/sw dir: modify onto del/rpl/mv"
  # WC state: any (D necessarily exists; children may have any state)
  test_tc_up_sw(sbox, d_mods, d_dels + d_rpls)

def up_sw_dir_del_onto_mod(sbox):
  "up/sw dir: del/rpl/mv onto modify"
  # WC state: any (D necessarily exists; children may have any state)
  test_tc_up_sw(sbox, d_dels + d_rpls, d_mods)

def up_sw_dir_del_onto_del(sbox):
  "up/sw dir: del/rpl/mv onto del/rpl/mv"
  # WC state: any (D necessarily exists; children may have any state)
  test_tc_up_sw(sbox, d_dels + d_rpls, d_dels + d_rpls)

# This is currently set as XFail over ra_dav because it hits
# issue #3314 'DAV can overwrite directories during copy'
#
#   TRUNK@35827.DBG>svn st -v branch1
#                   2        2 jrandom      branch1
#                   2        2 jrandom      branch1\dC
#   A  +            -        2 jrandom      branch1\dC\D
#
#   TRUNK@35827.DBG>svn log -r2:HEAD branch1 -v
#   ------------------------------------------------------------------------
#   r2 | jrandom | 2009-02-12 09:26:52 -0500 (Thu, 12 Feb 2009) | 1 line
#   Changed paths:
#      A /D1
#      A /F1
#      A /branch1
#      A /branch1/dC
#
#   Initial set-up.
#   ------------------------------------------------------------------------
#   r3 | jrandom | 2009-02-12 09:26:52 -0500 (Thu, 12 Feb 2009) | 1 line
#   Changed paths:
#      A /branch1/dC/D (from /D1:2)
#
#   Action.
#   ------------------------------------------------------------------------
#
#   TRUNK@35827.DBG>svn ci -m "Should be ood" branch1
#   Adding         branch1\dC\D
#
#   Committed revision 4.
def up_sw_dir_add_onto_add(sbox):
  "up/sw dir: add onto add"
  # WC state: as scheduled (no obstruction)
  test_tc_up_sw(sbox, d_adds, d_adds)

#----------------------------------------------------------------------

# Tests for merge affecting a file, where the incoming change
# conflicts with the target.

def merge_file_mod_onto_not_file(sbox):
  "merge file: modify onto not-file"
  sbox2 = sbox.clone_dependent()
  test_tc_merge(sbox, f_mods, br_scen = f_dels + f_rpl_d)
  test_tc_merge(sbox2, f_mods, wc_scen = f_dels)
  # Note: See UC4 in notes/tree-conflicts/use-cases.txt.

def merge_file_del_onto_not_same(sbox):
  "merge file: del/rpl/mv onto not-same"
  sbox2 = sbox.clone_dependent()
  test_tc_merge(sbox, f_dels + f_rpls, br_scen = f_mods)
  test_tc_merge(sbox2, f_dels + f_rpls, wc_scen = f_mods)
  # Note: See UC5 in notes/tree-conflicts/use-cases.txt.

def merge_file_del_onto_not_file(sbox):
  "merge file: del/rpl/mv onto not-file"
  sbox2 = sbox.clone_dependent()
  test_tc_merge(sbox, f_dels + f_rpls, br_scen = f_dels + f_rpl_d)
  test_tc_merge(sbox2, f_dels + f_rpls, wc_scen = f_dels)
  # Note: See UC6 in notes/tree-conflicts/use-cases.txt.

def merge_file_add_onto_not_none(sbox):
  "merge file: add onto not-none"
  sbox2 = sbox.clone_dependent()
  test_tc_merge(sbox, f_adds, br_scen = f_adds)  ### + d_adds (at path "F")
  test_tc_merge(sbox2, f_adds, wc_scen = f_adds)  ### + d_adds (at path "F")

#----------------------------------------------------------------------

# Tests for merge affecting a dir, where the incoming change
# conflicts with the target branch.

def merge_dir_mod_onto_not_dir(sbox):
  "merge dir: modify onto not-dir"
  sbox2 = sbox.clone_dependent()
  test_tc_merge(sbox, d_mods, br_scen = d_dels + d_rpl_f)
  test_tc_merge(sbox2, d_mods, wc_scen = d_dels)

def merge_dir_del_onto_not_same(sbox):
  "merge dir: del/rpl/mv onto not-same"
  sbox2 = sbox.clone_dependent()
  test_tc_merge(sbox, d_dels + d_rpls, br_scen = d_mods)
  test_tc_merge(sbox2, d_dels + d_rpls, wc_scen = d_mods)

def merge_dir_del_onto_not_dir(sbox):
  "merge dir: del/rpl/mv onto not-dir"
  sbox2 = sbox.clone_dependent()
  test_tc_merge(sbox, d_dels + d_rpls, br_scen = d_dels + d_rpl_f)
  test_tc_merge(sbox2, d_dels + d_rpls, wc_scen = d_dels)

def merge_dir_add_onto_not_none(sbox):
  "merge dir: add onto not-none"
  sbox2 = sbox.clone_dependent()
  test_tc_merge(sbox, d_adds, br_scen = d_adds)  ### + f_adds (at path "D")
  test_tc_merge(sbox2, d_adds, wc_scen = d_adds)  ### + f_adds (at path "D")


#######################################################################
# Run the tests


# list all tests here, starting with None:
test_list = [ None,
              up_sw_file_mod_onto_del,
              up_sw_file_del_onto_mod,
              up_sw_file_del_onto_del,
              up_sw_file_add_onto_add,
              up_sw_dir_mod_onto_del,
              up_sw_dir_del_onto_mod,
              up_sw_dir_del_onto_del,
              XFail(up_sw_dir_add_onto_add,
                    svntest.main.is_ra_type_dav),
              merge_file_mod_onto_not_file,
              merge_file_del_onto_not_same,
              merge_file_del_onto_not_file,
              merge_file_add_onto_not_none,
              merge_dir_mod_onto_not_dir,
              XFail(merge_dir_del_onto_not_same),
              merge_dir_del_onto_not_dir,
              merge_dir_add_onto_not_none,
             ]

if __name__ == '__main__':
  svntest.main.run_tests(test_list)
  # NOTREACHED


### End of file.