update_tests.py   [plain text]


#!/usr/bin/env python
#
#  update_tests.py:  testing update 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, subprocess

# Our testing module
import svntest
from svntest import wc
from merge_tests import set_up_branch

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

from svntest.main import SVN_PROP_MERGEINFO, server_sends_copyfrom_on_update, \
  server_has_mergeinfo

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


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

# Helper for update_binary_file() test -- a custom singleton handler.
def detect_extra_files(node, extra_files):
  """NODE has been discovered as an extra file on disk.  Verify that
  it matches one of the regular expressions in the EXTRA_FILES list of
  lists, and that its contents matches the second part of the list
  item.  If it matches, remove the match from the list.  If it doesn't
  match, raise an exception."""

  # Baton is of the form:
  #
  #       [ [wc_dir, pattern, contents],
  #         [wc_dir, pattern, contents], ... ]

  for fdata in extra_files:
    wc_dir = fdata[0]
    pattern = fdata[1]
    contents = None
    if len(fdata) > 2:
      contents = fdata[2]
    match_obj = re.match(pattern, node.name)
    if match_obj:
      if contents is None:
        return
      else:
        # Strip the root_node_name from node path
        # (svntest.tree.root_node_name, currently `__SVN_ROOT_NODE'),
        # since it doesn't really exist. Also strip the trailing "slash".
        real_path = node.path
        if real_path.startswith(svntest.tree.root_node_name):
          real_path = real_path[len(svntest.tree.root_node_name) +
                                len(os.sep) :]
        real_path = os.path.join(wc_dir, real_path)

        real_contents = svntest.main.file_read(real_path)
        if real_contents == contents:
          extra_files.pop(extra_files.index(fdata)) # delete pattern from list
          return

  print("Found unexpected object: %s" % node.name)
  raise svntest.tree.SVNTreeUnequal



def update_binary_file(sbox):
  "update a locally-modified binary file"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Add a binary file to the project.
  theta_contents = svntest.main.file_read(
    os.path.join(sys.path[0], "theta.bin"), 'rb')
  # Write PNG file data into 'A/theta'.
  theta_path = os.path.join(wc_dir, 'A', 'theta')
  svntest.main.file_write(theta_path, theta_contents, 'wb')

  svntest.main.run_svn(None, 'add', theta_path)

  # Created expected output tree for 'svn ci'
  expected_output = svntest.wc.State(wc_dir, {
    'A/theta' : Item(verb='Adding  (bin)'),
    })

  # Create expected status tree
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/theta' : Item(status='  ', wc_rev=2),
    })

  # Commit the new binary file, creating revision 2.
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Make a backup copy of the working copy.
  wc_backup = sbox.add_wc_path('backup')
  svntest.actions.duplicate_dir(wc_dir, wc_backup)
  theta_backup_path = os.path.join(wc_backup, 'A', 'theta')

  # Make a change to the binary file in the original working copy
  svntest.main.file_append(theta_path, "revision 3 text")
  theta_contents_r3 = theta_contents + "revision 3 text"

  # Created expected output tree for 'svn ci'
  expected_output = svntest.wc.State(wc_dir, {
    'A/theta' : Item(verb='Sending'),
    })

  # Create expected status tree
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/theta' : Item(status='  ', wc_rev=3),
    })

  # Commit original working copy again, creating revision 3.
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Now start working in the backup working copy:

  # Make a local mod to theta
  svntest.main.file_append(theta_backup_path, "extra theta text")
  theta_contents_local = theta_contents + "extra theta text"

  # Create expected output tree for an update of wc_backup.
  expected_output = svntest.wc.State(wc_backup, {
    'A/theta' : Item(status='C '),
    })

  # Create expected disk tree for the update --
  #    look!  binary contents, and a binary property!
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/theta' : Item(theta_contents_local,
                     props={'svn:mime-type' : 'application/octet-stream'}),
    })

  # Create expected status tree for the update.
  expected_status = svntest.actions.get_virginal_state(wc_backup, 3)
  expected_status.add({
    'A/theta' : Item(status='C ', wc_rev=3),
    })

  # Extra 'singleton' files we expect to exist after the update.
  # In the case, the locally-modified binary file should be backed up
  # to an .orig file.
  #  This is a list of lists, of the form [ WC_DIR,
  #                                         [pattern, contents], ...]
  extra_files = [[wc_backup, 'theta.*\.r2', theta_contents],
                 [wc_backup, 'theta.*\.r3', theta_contents_r3]]

  # Do the update and check the results in three ways.  Pass our
  # custom singleton handler to verify the .orig file; this handler
  # will verify the existence (and contents) of both binary files
  # after the update finishes.
  svntest.actions.run_and_verify_update(wc_backup,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None,
                                        detect_extra_files, extra_files,
                                        None, None, 1)

  # verify that the extra_files list is now empty.
  if len(extra_files) != 0:
    print("Not all extra reject files have been accounted for:")
    print(extra_files)
    raise svntest.Failure

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

def update_binary_file_2(sbox):
  "update to an old revision of a binary files"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Suck up contents of a test .png file.
  theta_contents = svntest.main.file_read(
    os.path.join(sys.path[0], "theta.bin"), 'rb')

  # 102400 is svn_txdelta_window_size.  We're going to make sure we
  # have at least 102401 bytes of data in our second binary file (for
  # no reason other than we have had problems in the past with getting
  # svndiff data out of the repository for files > 102400 bytes).
  # How?  Well, we'll just keep doubling the binary contents of the
  # original theta.png until we're big enough.
  zeta_contents = theta_contents
  while(len(zeta_contents) < 102401):
    zeta_contents = zeta_contents + zeta_contents

  # Write our two files' contents out to disk, in A/theta and A/zeta.
  theta_path = os.path.join(wc_dir, 'A', 'theta')
  svntest.main.file_write(theta_path, theta_contents, 'wb')
  zeta_path = os.path.join(wc_dir, 'A', 'zeta')
  svntest.main.file_write(zeta_path, zeta_contents, 'wb')

  # Now, `svn add' those two files.
  svntest.main.run_svn(None, 'add', theta_path, zeta_path)

  # Created expected output tree for 'svn ci'
  expected_output = svntest.wc.State(wc_dir, {
    'A/theta' : Item(verb='Adding  (bin)'),
    'A/zeta' : Item(verb='Adding  (bin)'),
    })

  # Create expected status tree
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/theta' : Item(status='  ', wc_rev=2),
    'A/zeta' : Item(status='  ', wc_rev=2),
    })

  # Commit the new binary filea, creating revision 2.
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Make some mods to the binary files.
  svntest.main.file_append(theta_path, "foobar")
  new_theta_contents = theta_contents + "foobar"
  svntest.main.file_append(zeta_path, "foobar")
  new_zeta_contents = zeta_contents + "foobar"

  # Created expected output tree for 'svn ci'
  expected_output = svntest.wc.State(wc_dir, {
    'A/theta' : Item(verb='Sending'),
    'A/zeta' : Item(verb='Sending'),
    })

  # Create expected status tree
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/theta' : Item(status='  ', wc_rev=3),
    'A/zeta' : Item(status='  ', wc_rev=3),
    })

  # Commit original working copy again, creating revision 3.
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Create expected output tree for an update to rev 2.
  expected_output = svntest.wc.State(wc_dir, {
    'A/theta' : Item(status='U '),
    'A/zeta' : Item(status='U '),
    })

  # Create expected disk tree for the update --
  #    look!  binary contents, and a binary property!
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/theta' : Item(theta_contents,
                     props={'svn:mime-type' : 'application/octet-stream'}),
    'A/zeta' : Item(zeta_contents,
                    props={'svn:mime-type' : 'application/octet-stream'}),
    })

  # Create expected status tree for the update.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'A/theta' : Item(status='  ', wc_rev=2),
    'A/zeta' : Item(status='  ', wc_rev=2),
    })

  # Do an update from revision 2 and make sure that our binary file
  # gets reverted to its original contents.
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None,
                                        None, None, 1,
                                        '-r', '2', wc_dir)


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

def update_missing(sbox):
  "update missing items (by name) in working copy"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Remove some files and dirs from the working copy.
  mu_path = os.path.join(wc_dir, 'A', 'mu')
  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
  E_path = os.path.join(wc_dir, 'A', 'B', 'E')
  H_path = os.path.join(wc_dir, 'A', 'D', 'H')

  # remove two files to verify that they get restored
  os.remove(mu_path)
  os.remove(rho_path)

  ### FIXME I think directories work because they generate 'A'
  ### feedback, is this the correct feedback?
  svntest.main.safe_rmtree(E_path)
  svntest.main.safe_rmtree(H_path)

  # Create expected output tree for an update of the missing items by name
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu'        : Item(verb='Restored'),
    'A/D/G/rho'   : Item(verb='Restored'),
    'A/B/E' : Item(status='A '),
    'A/B/E/alpha' : Item(status='A '),
    'A/B/E/beta' : Item(status='A '),
    'A/D/H' : Item(status='A '),
    'A/D/H/chi' : Item(status='A '),
    'A/D/H/omega' : Item(status='A '),
    'A/D/H/psi' : Item(status='A '),
    })

  # Create expected disk tree for the update.
  expected_disk = svntest.main.greek_state.copy()

  # Create expected status tree for the update.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)

  # Do the update and check the results in three ways.
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None, None, None, 0,
                                        mu_path, rho_path,
                                        E_path, H_path)

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

def update_ignores_added(sbox):
  "update should not munge adds or replaces"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Commit something so there's actually a new revision to update to.
  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
  svntest.main.file_append(rho_path, "More stuff in rho.\n")
  svntest.main.run_svn(None,
                       'ci', '-m', 'log msg', rho_path)

  # Create a new file, 'zeta', and schedule it for addition.
  zeta_path = os.path.join(wc_dir, 'A', 'B', 'zeta')
  svntest.main.file_append(zeta_path, "This is the file 'zeta'.\n")
  svntest.main.run_svn(None, 'add', zeta_path)

  # Schedule another file, say, 'gamma', for replacement.
  gamma_path = os.path.join(wc_dir, 'A', 'D', 'gamma')
  svntest.main.run_svn(None, 'delete', gamma_path)
  svntest.main.file_append(gamma_path, "This is a new 'gamma' now.\n")
  svntest.main.run_svn(None, 'add', gamma_path)

  # Now update.  "zeta at revision 0" should *not* be reported at all,
  # so it should remain scheduled for addition at revision 0.  gamma
  # was scheduled for replacement, so it also should remain marked as
  # such, and maintain its revision of 1.

  # Create expected output tree for an update of the wc_backup.
  expected_output = svntest.wc.State(wc_dir, { })

  # Create expected disk tree for the update.
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/B/zeta' : Item("This is the file 'zeta'.\n"),
    })
  expected_disk.tweak('A/D/gamma', contents="This is a new 'gamma' now.\n")
  expected_disk.tweak('A/D/G/rho',
                      contents="This is the file 'rho'.\nMore stuff in rho.\n")

  # Create expected status tree for the update.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.tweak('A/D/gamma', wc_rev=1, status='R ')
  expected_status.add({
    'A/B/zeta' : Item(status='A ', wc_rev=0),
    })

  # Do the update and check the results in three ways.
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status)


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

def update_to_rev_zero(sbox):
  "update to revision 0"

  sbox.build()
  wc_dir = sbox.wc_dir

  iota_path = os.path.join(wc_dir, 'iota')
  A_path = os.path.join(wc_dir, 'A')

  # Create expected output tree for an update to rev 0
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(status='D '),
    'A' : Item(status='D '),
    })

  # Create expected disk tree for the update to rev 0
  expected_disk = svntest.wc.State(wc_dir, { })

  # Do the update and check the results.
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        None, None,
                                        None, None, None, None, 0,
                                        '-r', '0', wc_dir)

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

def receive_overlapping_same_change(sbox):
  "overlapping identical changes should not conflict"

  ### (See http://subversion.tigris.org/issues/show_bug.cgi?id=682.)
  ###
  ### How this test works:
  ###
  ### Create working copy foo, modify foo/iota.  Duplicate foo,
  ### complete with locally modified iota, to bar.  Now we should
  ### have:
  ###
  ###    $ svn st foo
  ###    M    foo/iota
  ###    $ svn st bar
  ###    M    bar/iota
  ###    $
  ###
  ### Commit the change from foo, then update bar.  The repository
  ### change should get folded into bar/iota with no conflict, since
  ### the two modifications are identical.

  sbox.build()
  wc_dir = sbox.wc_dir

  # Modify iota.
  iota_path = os.path.join(wc_dir, 'iota')
  svntest.main.file_append(iota_path, "A change to iota.\n")

  # Duplicate locally modified wc, giving us the "other" wc.
  other_wc = sbox.add_wc_path('other')
  svntest.actions.duplicate_dir(wc_dir, other_wc)
  other_iota_path = os.path.join(other_wc, 'iota')

  # Created expected output tree for 'svn ci'
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(verb='Sending'),
    })

  # Create expected status tree
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', wc_rev=2)

  # Commit the change, creating revision 2.
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Expected output tree for update of other_wc.
  expected_output = svntest.wc.State(other_wc, {
    'iota' : Item(status='G '),
    })

  # Expected disk tree for the update.
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('iota',
                      contents="This is the file 'iota'.\nA change to iota.\n")

  # Expected status tree for the update.
  expected_status = svntest.actions.get_virginal_state(other_wc, 2)

  # Do the update and check the results in three ways.
  svntest.actions.run_and_verify_update(other_wc,
                                        expected_output,
                                        expected_disk,
                                        expected_status)

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

def update_to_resolve_text_conflicts(sbox):
  "delete files and update to resolve text conflicts"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Make a backup copy of the working copy
  wc_backup = sbox.add_wc_path('backup')
  svntest.actions.duplicate_dir(wc_dir, wc_backup)

  # Make a couple of local mods to files which will be committed
  mu_path = os.path.join(wc_dir, 'A', 'mu')
  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
  svntest.main.file_append(mu_path, 'Original appended text for mu\n')
  svntest.main.file_append(rho_path, 'Original appended text for rho\n')
  svntest.main.run_svn(None, 'propset', 'Kubla', 'Khan', rho_path)

  # Make a couple of local mods to files which will be conflicted
  mu_path_backup = os.path.join(wc_backup, 'A', 'mu')
  rho_path_backup = os.path.join(wc_backup, 'A', 'D', 'G', 'rho')
  svntest.main.file_append(mu_path_backup,
                           'Conflicting appended text for mu\n')
  svntest.main.file_append(rho_path_backup,
                           'Conflicting appended text for rho\n')
  svntest.main.run_svn(None, 'propset', 'Kubla', 'Xanadu', rho_path_backup)

  # Created expected output tree for 'svn ci'
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu' : Item(verb='Sending'),
    'A/D/G/rho' : Item(verb='Sending'),
    })

  # Create expected status tree; all local revisions should be at 1,
  # but mu and rho should be at revision 2.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  expected_status.tweak('A/D/G/rho', wc_rev=2, status='  ')

  # Commit.
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Create expected output tree for an update of the wc_backup.
  expected_output = svntest.wc.State(wc_backup, {
    'A/mu' : Item(status='C '),
    'A/D/G/rho' : Item(status='CC'),
    })

  # Create expected disk tree for the update.
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/mu',
                      contents="\n".join(["This is the file 'mu'.",
                                          "<<<<<<< .mine",
                                          "Conflicting appended text for mu",
                                          "=======",
                                          "Original appended text for mu",
                                          ">>>>>>> .r2",
                                          ""]))
  expected_disk.tweak('A/D/G/rho',
                      contents="\n".join(["This is the file 'rho'.",
                                          "<<<<<<< .mine",
                                          "Conflicting appended text for rho",
                                          "=======",
                                          "Original appended text for rho",
                                          ">>>>>>> .r2",
                                          ""]))

  # Create expected status tree for the update.
  expected_status = svntest.actions.get_virginal_state(wc_backup, 2)
  expected_status.tweak('A/mu', status='C ')
  expected_status.tweak('A/D/G/rho', status='CC')

  # "Extra" files that we expect to result from the conflicts.
  # These are expressed as list of regexps.  What a cool system!  :-)
  extra_files = ['mu.*\.r1', 'mu.*\.r2', 'mu.*\.mine',
                 'rho.*\.r1', 'rho.*\.r2', 'rho.*\.mine', 'rho.*\.prej']

  # Do the update and check the results in three ways.
  # All "extra" files are passed to detect_conflict_files().
  svntest.actions.run_and_verify_update(wc_backup,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None,
                                        svntest.tree.detect_conflict_files,
                                        extra_files)


  # verify that the extra_files list is now empty.
  if len(extra_files) != 0:
    print("didn't get expected extra files")
    raise svntest.Failure

  # remove the conflicting files to clear text conflict but not props conflict
  os.remove(mu_path_backup)
  os.remove(rho_path_backup)

  # ### TODO: Can't get run_and_verify_update to work here :-( I get
  # the error "Unequal Types: one Node is a file, the other is a
  # directory". Use run_svn and then run_and_verify_status instead
  exit_code, stdout_lines, stdout_lines = svntest.main.run_svn(None, 'up',
                                                               wc_backup)
  if len (stdout_lines) > 0:
    print("update 2 failed")
    raise svntest.Failure

  # Create expected status tree
  expected_status = svntest.actions.get_virginal_state(wc_backup, 2)
  expected_status.tweak('A/D/G/rho', status=' C')

  svntest.actions.run_and_verify_status(wc_backup, expected_status)

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

def update_delete_modified_files(sbox):
  "update that deletes modified files"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Delete a file
  alpha_path = os.path.join(wc_dir, 'A', 'B', 'E', 'alpha')
  svntest.actions.run_and_verify_svn("Deleting alpha failed", None, [],
                                     'rm', alpha_path)

  # Delete a directory containing files
  G_path = os.path.join(wc_dir, 'A', 'D', 'G')
  svntest.actions.run_and_verify_svn("Deleting G failed", None, [],
                                     'rm', G_path)

  # Commit
  svntest.actions.run_and_verify_svn("Committing deletes failed", None, [],
                                     'ci', '-m', 'log msg', wc_dir)

  # ### Update before backdating to avoid obstructed update error for G
  svntest.actions.run_and_verify_svn("Updating after commit failed", None, [],
                                     'up', wc_dir)

  # Backdate to restore deleted items
  svntest.actions.run_and_verify_svn("Backdating failed", None, [],
                                     'up', '-r', '1', wc_dir)

  # Modify the file to be deleted, and a file in the directory to be deleted
  svntest.main.file_append(alpha_path, 'appended alpha text\n')
  pi_path = os.path.join(G_path, 'pi')
  svntest.main.file_append(pi_path, 'appended pi text\n')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/B/E/alpha', 'A/D/G/pi', status='M ')

  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Now update to 'delete' modified items -- that is, remove them from
  # version control, but leave them on disk.  It used to be we would
  # expect an 'obstructed update' error (see issue #1196), then we
  # expected success (see issue #1806), and now we expect tree conflicts
  # (see issue #2282) on the missing or unversioned items.
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/E/alpha' : Item(status='  ', treeconflict='C'),
    'A/D/G'       : Item(status='  ', treeconflict='C'),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/B/E/alpha',
                      contents=\
                      "This is the file 'alpha'.\nappended alpha text\n")
  expected_disk.tweak('A/D/G/pi',
                      contents=\
                      "This is the file 'pi'.\nappended pi text\n")

  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  # A/B/E/alpha and the subtree rooted at A/D/G had local modificiations
  # prior to the update.  So there is a tree conflict and both A/B/E/alpha
  # A/D/G remain after the update, scheduled for addition as copies of
  # themselves from r1, along with the local modifications.
  expected_status.tweak('A/B/E/alpha', status='A ', copied='+', wc_rev='-',
                        treeconflict='C')
  expected_status.tweak('A/D/G/pi', status='M ')
  expected_status.tweak('A/D/G/pi', status='M ', copied='+', wc_rev='-')
  expected_status.tweak('A/D/G/rho', 'A/D/G/tau', status='  ', copied='+',
                        wc_rev='-')
  expected_status.tweak('A/D/G', status='A ', copied='+', wc_rev='-',
                        treeconflict='C')

  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status)

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

# Issue 847.  Doing an add followed by a remove for an item in state
# "deleted" caused the "deleted" state to get forgotten

def update_after_add_rm_deleted(sbox):
  "update after add/rm of deleted state"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Delete a file and directory from WC
  alpha_path = os.path.join(wc_dir, 'A', 'B', 'E', 'alpha')
  F_path = os.path.join(wc_dir, 'A', 'B', 'F')
  svntest.actions.run_and_verify_svn(None, None, [], 'rm', alpha_path, F_path)

  # Commit deletion
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/E/alpha' : Item(verb='Deleting'),
    'A/B/F'       : Item(verb='Deleting'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/B/E/alpha')
  expected_status.remove('A/B/F')
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # alpha and F are now in state "deleted", next we add a new ones
  svntest.main.file_append(alpha_path, "new alpha")
  svntest.actions.run_and_verify_svn(None, None, [], 'add', alpha_path)

  svntest.actions.run_and_verify_svn(None, None, [], 'mkdir', F_path)

  # New alpha and F should be in add state A
  expected_status.add({
    'A/B/E/alpha' : Item(status='A ', wc_rev=0),
    'A/B/F'       : Item(status='A ', wc_rev=0),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Forced removal of new alpha and F must restore "deleted" state

  svntest.actions.run_and_verify_svn(None, None, [], 'rm', '--force',
                                     alpha_path, F_path)
  if os.path.exists(alpha_path) or os.path.exists(F_path):
    raise svntest.Failure

  # "deleted" state is not visible in status
  expected_status.remove('A/B/E/alpha', 'A/B/F')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Although parent dir is already at rev 1, the "deleted" state will cause
  # alpha and F to be restored in the WC when updated to rev 1
  svntest.actions.run_and_verify_svn(None, None, [], 'up', '-r', '1', wc_dir)
  expected_status.add({
    'A/B/E/alpha' : Item(status='  ', wc_rev=1),
    'A/B/F'       : Item(status='  ', wc_rev=1),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

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

# Issue 1591.  Updating a working copy which contains local
# obstructions marks a directory as incomplete.  Removal of the
# obstruction and subsequent update should clear the "incomplete"
# flag.

def obstructed_update_alters_wc_props(sbox):
  "obstructed update alters WC properties"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Create a new dir in the repo in prep for creating an obstruction.
  #print "Adding dir to repo"
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'mkdir', '-m',
                                     'prep for obstruction',
                                     sbox.repo_url + '/A/foo')

  # Create an obstruction, a file in the WC with the same name as
  # present in a newer rev of the repo.
  #print "Creating obstruction"
  obstruction_parent_path = os.path.join(wc_dir, 'A')
  obstruction_path = os.path.join(obstruction_parent_path, 'foo')
  svntest.main.file_append(obstruction_path, 'an obstruction')

  # Update the WC to that newer rev to trigger the obstruction.
  #print "Updating WC"
  expected_output = svntest.wc.State(wc_dir, {})
  expected_disk = svntest.main.greek_state.copy()
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  error_re = 'Failed to add directory.*object of the same name already exists'
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        error_re)

  # Remove the file which caused the obstruction.
  #print "Removing obstruction"
  os.unlink(obstruction_path)

  # Update the -- now unobstructed -- WC again.
  #print "Updating WC again"
  expected_output = svntest.wc.State(wc_dir, {
    'A/foo' : Item(status='A '),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/foo' : Item(),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'A/foo' : Item(status='  ', wc_rev=2),
    })
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status)

  # The previously obstructed resource should now be in the WC.
  if not os.path.isdir(obstruction_path):
    raise svntest.Failure

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

# Issue 938.
def update_replace_dir(sbox):
  "update that replaces a directory"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Delete a directory
  F_path = os.path.join(wc_dir, 'A', 'B', 'F')
  svntest.actions.run_and_verify_svn(None, None, [], 'rm', F_path)

  # Commit deletion
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/F'       : Item(verb='Deleting'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/B/F')
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Add replacement directory
  svntest.actions.run_and_verify_svn(None, None, [], 'mkdir', F_path)

  # Commit addition
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/F'       : Item(verb='Adding'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/B/F', wc_rev=3)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Update to HEAD
  expected_output = svntest.wc.State(wc_dir, {
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_status = svntest.actions.get_virginal_state(wc_dir, 3)
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status)

  # Update to revision 1 replaces the directory
  ### I can't get this to work :-(
  #expected_output = svntest.wc.State(wc_dir, {
  #  'A/B/F'       : Item(verb='Adding'),
  #  'A/B/F'       : Item(verb='Deleting'),
  #  })
  #expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  #svntest.actions.run_and_verify_update(wc_dir,
  #                                      expected_output,
  #                                      expected_disk,
  #                                      expected_status,
  #                                      None, None, None, None, None, 0,
  #                                      '-r', '1', wc_dir)

  # Update to revision 1 replaces the directory
  svntest.actions.run_and_verify_svn(None, None, [], 'up', '-r', '1', wc_dir)
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

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

def update_single_file(sbox):
  "update with explicit file target"

  sbox.build()
  wc_dir = sbox.wc_dir

  expected_disk = svntest.main.greek_state.copy()

  # Make a local mod to a file which will be committed
  mu_path = os.path.join(wc_dir, 'A', 'mu')
  svntest.main.file_append(mu_path, '\nAppended text for mu')

  # Commit.
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu' : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # At one stage 'svn up file' failed with a parent lock error
  was_cwd = os.getcwd()
  os.chdir(os.path.join(wc_dir, 'A'))

  ### Can't get run_and_verify_update to work having done the chdir.
  svntest.actions.run_and_verify_svn("update failed", None, [],
                                     'up', '-r', '1', 'mu')
  os.chdir(was_cwd)

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

#----------------------------------------------------------------------
def prop_update_on_scheduled_delete(sbox):
  "receive prop update to file scheduled for deletion"

  sbox.build()
  wc_dir = sbox.wc_dir

  other_wc = sbox.add_wc_path('other')

  # Make the "other" working copy.
  svntest.actions.duplicate_dir(wc_dir, other_wc)

  iota_path = os.path.join(wc_dir, 'iota')
  other_iota_path = os.path.join(other_wc, 'iota')

  svntest.main.run_svn(None, 'propset', 'foo', 'bar', iota_path)

  # Created expected output tree for 'svn ci'
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(verb='Sending'),
    })

  # Create expected status tree
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', wc_rev=2)

  # Commit the change, creating revision 2.
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  svntest.main.run_svn(None, 'rm', other_iota_path)

  # Expected output tree for update of other_wc.
  expected_output = svntest.wc.State(other_wc, {
    'iota' : Item(status='  ', treeconflict='C'),
    })

  # Expected disk tree for the update.
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('iota')

  # Expected status tree for the update.
  expected_status = svntest.actions.get_virginal_state(other_wc, 2)
  expected_status.tweak('iota', status='D ', treeconflict='C')

  # Do the update and check the results in three ways.
  svntest.actions.run_and_verify_update(other_wc,
                                        expected_output,
                                        expected_disk,
                                        expected_status)

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

def update_receive_illegal_name(sbox):
  "bail when receive a file or dir named .svn"

  sbox.build()
  wc_dir = sbox.wc_dir

  # This tests the revision 4334 fix for issue #1068.

  legal_url = sbox.repo_url + '/A/D/G/svn'
  illegal_url = (sbox.repo_url
                 + '/A/D/G/' + svntest.main.get_admin_name())
  # Ha!  The client doesn't allow us to mkdir a '.svn' but it does
  # allow us to copy to a '.svn' so ...
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'mkdir', '-m', 'log msg',
                                     legal_url)
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'mv', '-m', 'log msg',
                                     legal_url, illegal_url)

  # Do the update twice, both should fail.  After the first failure
  # the wc will be marked "incomplete".
  for n in range(2):
    exit_code, out, err = svntest.main.run_svn(1, 'up', wc_dir)
    for line in err:
      if line.find("an unversioned directory of the same " \
                   "name already exists") != -1:
        break
    else:
      raise svntest.Failure

  # At one stage an obstructed update in an incomplete wc would leave
  # a txn behind
  exit_code, out, err = svntest.main.run_svnadmin('lstxns', sbox.repo_dir)
  if out or err:
    raise svntest.Failure

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

def update_deleted_missing_dir(sbox):
  "update missing dir to rev in which it is absent"

  sbox.build()
  wc_dir = sbox.wc_dir

  E_path = os.path.join(wc_dir, 'A', 'B', 'E')
  H_path = os.path.join(wc_dir, 'A', 'D', 'H')

  # Create a new revision with directories deleted
  svntest.main.run_svn(None, 'rm', E_path)
  svntest.main.run_svn(None, 'rm', H_path)
  svntest.main.run_svn(None,
                       'ci', '-m', 'log msg', E_path, H_path)

  # Update back to the old revision
  svntest.main.run_svn(None,
                       'up', '-r', '1', wc_dir)

  # Delete the directories from disk
  svntest.main.safe_rmtree(E_path)
  svntest.main.safe_rmtree(H_path)

  # Create expected output tree for an update of the missing items by name
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/E' : Item(status='D '),
    'A/D/H' : Item(status='D '),
    })

  # Create expected disk tree for the update.
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/B/E', 'A/B/E/alpha', 'A/B/E/beta')
  expected_disk.remove('A/D/H', 'A/D/H/chi', 'A/D/H/omega', 'A/D/H/psi')

  # Create expected status tree for the update.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/B/E', 'A/B/E/alpha', 'A/B/E/beta')
  expected_status.remove('A/D/H', 'A/D/H/chi', 'A/D/H/omega', 'A/D/H/psi')

  # Do the update, specifying the deleted paths explicitly.
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None, None, None,
                                        0, "-r", "2", E_path, H_path)

  # Update back to the old revision again
  svntest.main.run_svn(None,
                       'up', '-r', '1', wc_dir)

  # This time we're updating the whole working copy
  expected_status.tweak(wc_rev=2)

  # Do the update, on the whole working copy this time
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None, None, None,
                                        0, "-r", "2", wc_dir)

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

# Issue 919.  This test was written as a regression test for "item
# should remain 'deleted' when an update deletes a sibling".
def another_hudson_problem(sbox):
  "another \"hudson\" problem: updates that delete"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Delete/commit gamma thus making it 'deleted'
  gamma_path = os.path.join(wc_dir, 'A', 'D', 'gamma')
  svntest.main.run_svn(None, 'rm', gamma_path)
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/gamma' : Item(verb='Deleting'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/D/gamma')
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None, wc_dir)

  # Delete directory G from the repository
  svntest.actions.run_and_verify_svn(None,
                                     ['\n', 'Committed revision 3.\n'], [],
                                     'rm', '-m', 'log msg',
                                     sbox.repo_url + '/A/D/G')

  # Remove corresponding tree from working copy
  G_path = os.path.join(wc_dir, 'A', 'D', 'G')
  svntest.main.safe_rmtree(G_path)

  # Update missing directory to receive the delete, this should mark G
  # as 'deleted' and should not alter gamma's entry.

  # Sigh, I can't get run_and_verify_update to work (but not because
  # of issue 919 as far as I can tell)
  svntest.actions.run_and_verify_svn(None,
                                     ['D    '+G_path+'\n',
                                      'Updated to revision 3.\n',
                                      ], [],
                                     'up', G_path)

  # Both G and gamma should be 'deleted', update should produce no output
  expected_status = svntest.actions.get_virginal_state(wc_dir, 3)
  expected_status.remove('A/D/G', 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau',
                         'A/D/gamma')

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/D/G', 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau',
                       'A/D/gamma')
  svntest.actions.run_and_verify_update(wc_dir,
                                        "",
                                        expected_disk,
                                        expected_status)

#----------------------------------------------------------------------
def update_deleted_targets(sbox):
  "explicit update of deleted=true targets"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Delete/commit thus creating 'deleted=true' entries
  gamma_path = os.path.join(wc_dir, 'A', 'D', 'gamma')
  F_path = os.path.join(wc_dir, 'A', 'B', 'F')
  svntest.main.run_svn(None, 'rm', gamma_path, F_path)
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/gamma' : Item(verb='Deleting'),
    'A/B/F'     : Item(verb='Deleting'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/D/gamma', 'A/B/F')
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None, wc_dir)

  # Explicit update must not remove the 'deleted=true' entries
  svntest.actions.run_and_verify_svn(None, ['At revision 2.\n'], [],
                                     'update', gamma_path)
  svntest.actions.run_and_verify_svn(None, ['At revision 2.\n'], [],
                                     'update', F_path)

  # Update to r1 to restore items, since the parent directory is already
  # at r1 this fails if the 'deleted=true' entries are missing (issue 2250)
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/gamma' : Item(status='A '),
    'A/B/F'     : Item(status='A '),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_disk = svntest.main.greek_state.copy()
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None, None, None, 0,
                                        '-r', '1', wc_dir)



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

def new_dir_with_spaces(sbox):
  "receive new dir with spaces in its name"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Create a new directory ("spacey dir") directly in repository
  svntest.actions.run_and_verify_svn(None,
                                     ['\n', 'Committed revision 2.\n'], [],
                                     'mkdir', '-m', 'log msg',
                                     sbox.repo_url
                                     + '/A/spacey%20dir')

  # Update, and make sure ra_neon doesn't choke on the space.
  expected_output = svntest.wc.State(wc_dir, {
    'A/spacey dir'       : Item(status='A '),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'A/spacey dir'       : Item(status='  ', wc_rev=2),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/spacey dir' : Item(),
    })

  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status)

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

def non_recursive_update(sbox):
  "non-recursive update"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Commit a change to A/mu and A/D/G/rho
  mu_path = os.path.join(wc_dir, 'A', 'mu')
  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
  svntest.main.file_append(mu_path, "new")
  svntest.main.file_append(rho_path, "new")
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu' : Item(verb='Sending'),
    'A/D/G/rho' : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', 'A/D/G/rho', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status,
                                        None, wc_dir)

  # Update back to revision 1
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu' : Item(status='U '),
    'A/D/G/rho' : Item(status='U '),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_status.tweak('A/mu', 'A/D/G/rho', wc_rev=1)
  svntest.actions.run_and_verify_update(wc_dir, expected_output,
                                        expected_disk, expected_status,
                                        None, None, None, None, None, 0,
                                        '-r', '1', wc_dir)

  # Non-recursive update of A should change A/mu but not A/D/G/rho
  A_path = os.path.join(wc_dir, 'A')
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu' : Item(status='U '),
    })
  expected_status.tweak('A', 'A/mu', wc_rev=2)
  expected_disk.tweak('A/mu', contents="This is the file 'mu'.\nnew")
  svntest.actions.run_and_verify_update(wc_dir, expected_output,
                                        expected_disk, expected_status,
                                        None, None, None, None, None, 0,
                                        '-N', A_path)

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

def checkout_empty_dir(sbox):
  "check out an empty dir"
  # See issue #1472 -- checked out empty dir should not be marked as
  # incomplete ("!" in status).
  sbox.build(create_wc = False)
  wc_dir = sbox.wc_dir

  C_url = sbox.repo_url + '/A/C'

  svntest.main.safe_rmtree(wc_dir)
  svntest.actions.run_and_verify_svn(None, None, [], 'checkout', C_url, wc_dir)

  svntest.actions.run_and_verify_svn(None, [], [], 'status', wc_dir)


#----------------------------------------------------------------------
# Regression test for issue #919: "another ghudson bug".  Basically, if
# we fore- or back-date an item until it no longer exists, we were
# completely removing the entry, rather than marking it 'deleted'
# (which we now do.)

def update_to_deletion(sbox):
  "update target till it's gone, then get it back"

  sbox.build()
  wc_dir = sbox.wc_dir

  iota_path = os.path.join(wc_dir, 'iota')

  # Update iota to rev 0, so it gets removed.
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(status='D '),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('iota')

  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        None, None,
                                        None, None, None, None, 0,
                                        '-r', '0', iota_path)

  # Update the wc root, so iota comes back.
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(status='A '),
    })
  expected_disk = svntest.main.greek_state.copy()

  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        None, None,
                                        None, None, None, None, 0,
                                        wc_dir)


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

def update_deletion_inside_out(sbox):
  "update child before parent of a deleted tree"

  sbox.build()
  wc_dir = sbox.wc_dir

  parent_path = os.path.join(wc_dir, 'A', 'B')
  child_path = os.path.join(parent_path, 'E')  # Could be a file, doesn't matter

  # Delete the parent directory.
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'rm', parent_path)
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'ci', '-m', '', wc_dir)

  # Update back to r1.
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'update', '-r', '1', wc_dir)

  # Update just the child to r2.
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'update', '-r', '2', child_path)

  # Now try a normal update.
  expected_output = svntest.wc.State(wc_dir, {
    'A/B' : Item(status='D '),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/B', 'A/B/lambda', 'A/B/F',
                       'A/B/E', 'A/B/E/alpha', 'A/B/E/beta')

  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        None)


#----------------------------------------------------------------------
# Regression test for issue #1793, whereby 'svn up dir' would delete
# dir if schedule-add.  Yikes.

def update_schedule_add_dir(sbox):
  "update a schedule-add directory"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Delete directory A/D/G in the repository via immediate commit
  G_path = os.path.join(wc_dir, 'A', 'D', 'G')
  G_url = sbox.repo_url + '/A/D/G'
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'rm', G_url, '-m', 'rev 2')

  # Update the wc to HEAD (r2)
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/G' : Item(status='D '),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/D/G', 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau')
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.remove('A/D/G', 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau')
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status)

  # Do a URL->wc copy, creating a new schedule-add A/D/G.
  # (Standard procedure when trying to resurrect the directory.)
  D_path = os.path.join(wc_dir, 'A', 'D')
  svntest.actions.run_and_verify_svn("Copy error:", None, [],
                                     'cp', G_url + '@1', D_path)

  # status should now show the dir scheduled for addition-with-history
  expected_status.add({
    'A/D/G'     : Item(status='A ', copied='+', wc_rev='-'),
    'A/D/G/pi'  : Item(status='  ', copied='+', wc_rev='-'),
    'A/D/G/rho' : Item(status='  ', copied='+', wc_rev='-'),
    'A/D/G/tau' : Item(status='  ', copied='+', wc_rev='-'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Now update with the schedule-add dir as the target.
  svntest.actions.run_and_verify_svn(None, None, [], 'up', G_path)

  # The update should be a no-op, and the schedule-add directory
  # should still exist!  'svn status' shouldn't change at all.
  svntest.actions.run_and_verify_status(wc_dir, expected_status)


#----------------------------------------------------------------------
# Test updating items that do not exist in the current WC rev, but do
# exist at some future revision.

def update_to_future_add(sbox):
  "update target that was added in a future rev"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Update the entire WC to rev 0
  # Create expected output tree for an update to rev 0
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(status='D '),
    'A' : Item(status='D '),
    })

  # Create expected disk tree for the update to rev 0
  expected_disk = svntest.wc.State(wc_dir, { })

  # Do the update and check the results.
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        None, None,
                                        None, None, None, None, 0,
                                        '-r', '0', wc_dir)

  # Update iota to the current HEAD.
  iota_path = os.path.join(wc_dir, 'iota')
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(status='A '),
    })
  expected_disk = svntest.wc.State('', {
   'iota' : Item("This is the file 'iota'.\n")
   })

  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        None, None,
                                        None, None, None, None, 0,
                                        iota_path)

  # Now try updating the directory into the future
  A_path = os.path.join(wc_dir, 'A')

  expected_output = svntest.wc.State(wc_dir, {
    'A'              : Item(status='A '),
    'A/mu'           : Item(status='A '),
    'A/B'            : Item(status='A '),
    'A/B/lambda'     : Item(status='A '),
    'A/B/E'          : Item(status='A '),
    'A/B/E/alpha'    : Item(status='A '),
    'A/B/E/beta'     : Item(status='A '),
    'A/B/F'          : Item(status='A '),
    'A/C'            : Item(status='A '),
    'A/D'            : Item(status='A '),
    'A/D/gamma'      : Item(status='A '),
    'A/D/G'          : Item(status='A '),
    'A/D/G/pi'       : Item(status='A '),
    'A/D/G/rho'      : Item(status='A '),
    'A/D/G/tau'      : Item(status='A '),
    'A/D/H'          : Item(status='A '),
    'A/D/H/chi'      : Item(status='A '),
    'A/D/H/psi'      : Item(status='A '),
    'A/D/H/omega'    : Item(status='A ')
    })
  expected_disk = svntest.main.greek_state.copy()

  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        None, None,
                                        None, None, None, None, 0,
                                        A_path);

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

def nested_in_read_only(sbox):
  "update a nested wc in a read-only wc"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Delete/commit a file
  alpha_path = os.path.join(wc_dir, 'A', 'B', 'E', 'alpha')
  svntest.actions.run_and_verify_svn(None, None, [], 'rm', alpha_path)
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/E/alpha' : Item(verb='Deleting'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/B/E/alpha')
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)
  svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
  expected_status.tweak(wc_rev=2)
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Delete/commit a directory that used to contain the deleted file
  B_path = os.path.join(wc_dir, 'A', 'B')
  svntest.actions.run_and_verify_svn(None, None, [], 'rm', B_path)
  expected_output = svntest.wc.State(wc_dir, {
    'A/B' : Item(verb='Deleting'),
    })
  expected_status.remove('A/B', 'A/B/lambda', 'A/B/E', 'A/B/E/beta', 'A/B/F')
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
  expected_status.tweak(wc_rev=3)
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Replace the deleted directory with a new checkout of an old
  # version of the directory, this gives it a "plausible" URL that
  # could be part of the containing wc
  B_url = sbox.repo_url + '/A/B'
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'checkout', '-r', '1', B_url + "@1",
                                     B_path)
  expected_status = svntest.wc.State(B_path, {
    ''           : Item(),
    'lambda'     : Item(),
    'E'          : Item(),
    'E/alpha'    : Item(),
    'E/beta'     : Item(),
    'F'          : Item(),
    })
  expected_status.tweak(wc_rev=1, status='  ')
  svntest.actions.run_and_verify_status(B_path, expected_status)

  # Make enclosing wc read only
  os.chmod(os.path.join(wc_dir, 'A', svntest.main.get_admin_name()), 0555)

  try:
    # Update of nested wc should still work
    expected_output = svntest.wc.State(B_path, {
      'E/alpha' : Item(status='D '),
      })
    expected_disk = wc.State('', {
      'lambda'  : wc.StateItem("This is the file 'lambda'.\n"),
      'E'       : wc.StateItem(),
      'E/beta'  : wc.StateItem("This is the file 'beta'.\n"),
      'F'       : wc.StateItem(),
      })
    expected_status.remove('E/alpha')
    expected_status.tweak(wc_rev=2)
    svntest.actions.run_and_verify_update(B_path,
                                          expected_output,
                                          expected_disk,
                                          expected_status,
                                          None, None, None, None, None, 0,
                                          '-r', '2', B_path)
  finally:
    os.chmod(os.path.join(wc_dir, 'A', svntest.main.get_admin_name()), 0777)

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

def update_xml_unsafe_dir(sbox):
  "update dir with xml-unsafe name"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Make a backup copy of the working copy
  wc_backup = sbox.add_wc_path('backup')
  svntest.actions.duplicate_dir(wc_dir, wc_backup)

  # Make a couple of local mods to files
  test_path = os.path.join(wc_dir, ' foo & bar')
  svntest.main.run_svn(None, 'mkdir', test_path)

  # Created expected output tree for 'svn ci'
  expected_output = wc.State(wc_dir, {
    ' foo & bar' : Item(verb='Adding'),
    })

  # Create expected status tree; all local revisions should be at 1,
  # but 'foo & bar' should be at revision 2.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    ' foo & bar' : Item(status='  ', wc_rev=2),
    })

  # Commit.
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # chdir into the funky path, and update from there.
  os.chdir(test_path)

  expected_output = wc.State('', {
    })
  expected_disk = wc.State('', {
    })
  expected_status = wc.State('', {
    '' : Item(status='  ', wc_rev=2),
    })
  svntest.actions.run_and_verify_update('', expected_output, expected_disk,
                                        expected_status)

#----------------------------------------------------------------------
# eol-style handling during update with conflicts, scenario 1:
# when update creates a conflict on a file, make sure the file and files
# r<left>, r<right> and .mine are in the eol-style defined for that file.
#
# This test for 'svn merge' can be found in merge_tests.py as
# merge_conflict_markers_matching_eol.
def conflict_markers_matching_eol(sbox):
  "conflict markers should match the file's eol style"

  sbox.build()
  wc_dir = sbox.wc_dir
  filecount = 1

  mu_path = os.path.join(wc_dir, 'A', 'mu')

  if os.name == 'nt':
    crlf = '\n'
  else:
    crlf = '\r\n'

  # Checkout a second working copy
  wc_backup = sbox.add_wc_path('backup')
  svntest.actions.run_and_verify_svn(None, None, [], 'checkout',
                                     sbox.repo_url, wc_backup)

  # set starting revision
  cur_rev = 1

  expected_disk = svntest.main.greek_state.copy()
  expected_status = svntest.actions.get_virginal_state(wc_dir, cur_rev)
  expected_backup_status = svntest.actions.get_virginal_state(wc_backup,
                                                              cur_rev)

  path_backup = os.path.join(wc_backup, 'A', 'mu')

  # do the test for each eol-style
  for eol, eolchar in zip(['CRLF', 'CR', 'native', 'LF'],
                          [crlf, '\015', '\n', '\012']):
    # rewrite file mu and set the eol-style property.
    svntest.main.file_write(mu_path, "This is the file 'mu'."+ eolchar, 'wb')
    svntest.main.run_svn(None, 'propset', 'svn:eol-style', eol, mu_path)

    expected_disk.add({
      'A/mu' : Item("This is the file 'mu'." + eolchar)
    })
    expected_output = svntest.wc.State(wc_dir, {
      'A/mu' : Item(verb='Sending'),
    })
    expected_status.tweak(wc_rev = cur_rev)
    expected_status.add({
      'A/mu' : Item(status='  ', wc_rev = cur_rev + 1),
    })

    # Commit the original change and note the 'base' revision number
    svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                          expected_status, None, wc_dir)
    cur_rev = cur_rev + 1
    base_rev = cur_rev

    svntest.main.run_svn(None, 'update', wc_backup)

    # Make a local mod to mu
    svntest.main.file_append(mu_path,
                             'Original appended text for mu' + eolchar)

    # Commit the original change and note the 'theirs' revision number
    svntest.main.run_svn(None, 'commit', '-m', 'test log', wc_dir)
    cur_rev = cur_rev + 1
    theirs_rev = cur_rev

    # Make a local mod to mu, will conflict with the previous change
    svntest.main.file_append(path_backup,
                             'Conflicting appended text for mu' + eolchar)

    # Create expected output tree for an update of the wc_backup.
    expected_backup_output = svntest.wc.State(wc_backup, {
      'A/mu' : Item(status='C '),
      })

    # Create expected disk tree for the update.
    expected_backup_disk = expected_disk.copy()

    # verify content of resulting conflicted file
    expected_backup_disk.add({
    'A/mu' : Item(contents= "This is the file 'mu'." + eolchar +
      "<<<<<<< .mine" + eolchar +
      "Conflicting appended text for mu" + eolchar +
      "=======" + eolchar +
      "Original appended text for mu" + eolchar +
      ">>>>>>> .r" + str(cur_rev) + eolchar),
    })
    # verify content of base(left) file
    expected_backup_disk.add({
    'A/mu.r' + str(base_rev ) : Item(contents= "This is the file 'mu'." +
      eolchar)
    })
    # verify content of theirs(right) file
    expected_backup_disk.add({
    'A/mu.r' + str(theirs_rev ) : Item(contents= "This is the file 'mu'." +
      eolchar +
      "Original appended text for mu" + eolchar)
    })
    # verify content of mine file
    expected_backup_disk.add({
    'A/mu.mine' : Item(contents= "This is the file 'mu'." +
      eolchar +
      "Conflicting appended text for mu" + eolchar)
    })

    # Create expected status tree for the update.
    expected_backup_status.add({
      'A/mu'   : Item(status='  ', wc_rev=cur_rev),
    })
    expected_backup_status.tweak('A/mu', status='C ')
    expected_backup_status.tweak(wc_rev = cur_rev)

    # Do the update and check the results in three ways.
    svntest.actions.run_and_verify_update(wc_backup,
                                          expected_backup_output,
                                          expected_backup_disk,
                                          expected_backup_status,
                                          None,
                                          None,
                                          None)

    # cleanup for next run
    svntest.main.run_svn(None, 'revert', '-R', wc_backup)
    svntest.main.run_svn(None, 'update', wc_dir)

# eol-style handling during update, scenario 2:
# if part of that update is a propchange (add, change, delete) of
# svn:eol-style, make sure the correct eol-style is applied before
# calculating the merge (and conflicts if any)
#
# This test for 'svn merge' can be found in merge_tests.py as
# merge_eolstyle_handling.
def update_eolstyle_handling(sbox):
  "handle eol-style propchange during update"

  sbox.build()
  wc_dir = sbox.wc_dir

  mu_path = os.path.join(wc_dir, 'A', 'mu')

  if os.name == 'nt':
    crlf = '\n'
  else:
    crlf = '\r\n'

  # Checkout a second working copy
  wc_backup = sbox.add_wc_path('backup')
  svntest.actions.run_and_verify_svn(None, None, [], 'checkout',
                                     sbox.repo_url, wc_backup)
  path_backup = os.path.join(wc_backup, 'A', 'mu')

  # Test 1: add the eol-style property and commit, change mu in the second
  # working copy and update; there should be no conflict!
  svntest.main.run_svn(None, 'propset', 'svn:eol-style', "CRLF", mu_path)
  svntest.main.run_svn(None,
                       'commit', '-m', 'set eol-style property', wc_dir)

  svntest.main.file_append_binary(path_backup, 'Added new line of text.\012')

  expected_backup_disk = svntest.main.greek_state.copy()
  expected_backup_disk.tweak(
  'A/mu', contents= "This is the file 'mu'." + crlf +
    "Added new line of text." + crlf)
  expected_backup_output = svntest.wc.State(wc_backup, {
    'A/mu' : Item(status='GU'),
    })
  expected_backup_status = svntest.actions.get_virginal_state(wc_backup, 2)
  expected_backup_status.tweak('A/mu', status='M ')

  svntest.actions.run_and_verify_update(wc_backup,
                                        expected_backup_output,
                                        expected_backup_disk,
                                        expected_backup_status,
                                        None, None, None)

  # Test 2: now change the eol-style property to another value and commit,
  # update the still changed mu in the second working copy; there should be
  # no conflict!
  svntest.main.run_svn(None, 'propset', 'svn:eol-style', "CR", mu_path)
  svntest.main.run_svn(None,
                       'commit', '-m', 'set eol-style property', wc_dir)

  expected_backup_disk = svntest.main.greek_state.copy()
  expected_backup_disk.add({
  'A/mu' : Item(contents= "This is the file 'mu'.\015" +
    "Added new line of text.\015")
  })
  expected_backup_output = svntest.wc.State(wc_backup, {
    'A/mu' : Item(status='GU'),
    })
  expected_backup_status = svntest.actions.get_virginal_state(wc_backup, 3)
  expected_backup_status.tweak('A/mu', status='M ')
  svntest.actions.run_and_verify_update(wc_backup,
                                        expected_backup_output,
                                        expected_backup_disk,
                                        expected_backup_status,
                                        None, None, None)

  # Test 3: now delete the eol-style property and commit, update the still
  # changed mu in the second working copy; there should be no conflict!
  # EOL of mu should be unchanged (=CR).
  svntest.main.run_svn(None, 'propdel', 'svn:eol-style', mu_path)
  svntest.main.run_svn(None,
                       'commit', '-m', 'del eol-style property', wc_dir)

  expected_backup_disk = svntest.main.greek_state.copy()
  expected_backup_disk.add({
  'A/mu' : Item(contents= "This is the file 'mu'.\015" +
    "Added new line of text.\015")
  })
  expected_backup_output = svntest.wc.State(wc_backup, {
    'A/mu' : Item(status=' U'),
    })
  expected_backup_status = svntest.actions.get_virginal_state(wc_backup, 4)
  expected_backup_status.tweak('A/mu', status='M ')
  svntest.actions.run_and_verify_update(wc_backup,
                                        expected_backup_output,
                                        expected_backup_disk,
                                        expected_backup_status,
                                        None, None, None)

# Bug in which "update" put a bogus revision number on a schedule-add file,
# causing the wrong version of it to be committed.
def update_copy_of_old_rev(sbox):
  "update schedule-add copy of old rev"

  sbox.build()
  wc_dir = sbox.wc_dir

  dir = os.path.join(wc_dir, 'A')
  dir2 = os.path.join(wc_dir, 'A2')
  file = os.path.join(dir, 'mu')
  file2 = os.path.join(dir2, 'mu')
  url = sbox.repo_url + '/A/mu'
  url2 = sbox.repo_url + '/A2/mu'

  # Remember the original text of the file
  exit_code, text_r1, err = svntest.actions.run_and_verify_svn(None, None, [],
                                                               'cat', '-r1',
                                                               url)

  # Commit a different version of the file
  svntest.main.file_write(file, "Second revision of 'mu'\n")
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'ci', '-m', '', wc_dir)

  # Copy an old revision of its directory into a new path in the WC
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'cp', '-r1', dir, dir2)

  # Update.  (Should do nothing, but added a bogus "revision" in "entries".)
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'up', wc_dir)

  # Commit, and check that it says it's committing the right thing
  exp_out = ['Adding         ' + dir2 + '\n',
             '\n',
             'Committed revision 3.\n']
  svntest.actions.run_and_verify_svn(None, exp_out, [],
                                     'ci', '-m', '', wc_dir)

  # Verify the committed file's content
  svntest.actions.run_and_verify_svn(None, text_r1, [],
                                     'cat', url2)

#----------------------------------------------------------------------
def forced_update(sbox):
  "forced update tolerates obstructions to adds"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Make a backup copy of the working copy
  wc_backup = sbox.add_wc_path('backup')
  svntest.actions.duplicate_dir(wc_dir, wc_backup)

  # Make a couple of local mods to files
  mu_path = os.path.join(wc_dir, 'A', 'mu')
  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
  svntest.main.file_append(mu_path, 'appended mu text')
  svntest.main.file_append(rho_path, 'new appended text for rho')

  # Add some files
  nu_path = os.path.join(wc_dir, 'A', 'B', 'F', 'nu')
  svntest.main.file_append(nu_path, "This is the file 'nu'\n")
  svntest.main.run_svn(None, 'add', nu_path)
  kappa_path = os.path.join(wc_dir, 'kappa')
  svntest.main.file_append(kappa_path, "This is the file 'kappa'\n")
  svntest.main.run_svn(None, 'add', kappa_path)

  # Add a dir with two files
  I_path = os.path.join(wc_dir, 'A', 'C', 'I')
  os.mkdir(I_path)
  svntest.main.run_svn(None, 'add', I_path)
  upsilon_path = os.path.join(I_path, 'upsilon')
  svntest.main.file_append(upsilon_path, "This is the file 'upsilon'\n")
  svntest.main.run_svn(None, 'add', upsilon_path)
  zeta_path = os.path.join(I_path, 'zeta')
  svntest.main.file_append(zeta_path, "This is the file 'zeta'\n")
  svntest.main.run_svn(None, 'add', zeta_path)

  # Created expected output tree for 'svn ci'
  expected_output = wc.State(wc_dir, {
    'A/mu'          : Item(verb='Sending'),
    'A/D/G/rho'     : Item(verb='Sending'),
    'A/B/F/nu'      : Item(verb='Adding'),
    'kappa'         : Item(verb='Adding'),
    'A/C/I'         : Item(verb='Adding'),
    'A/C/I/upsilon' : Item(verb='Adding'),
    'A/C/I/zeta'    : Item(verb='Adding'),
    })

  # Create expected status tree.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/B/F/nu'      : Item(status='  ', wc_rev=2),
    'kappa'         : Item(status='  ', wc_rev=2),
    'A/C/I'         : Item(status='  ', wc_rev=2),
    'A/C/I/upsilon' : Item(status='  ', wc_rev=2),
    'A/C/I/zeta'    : Item(status='  ', wc_rev=2),
    })
  expected_status.tweak('A/mu', 'A/D/G/rho', wc_rev=2)

  # Commit.
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Make a local mod to mu that will merge cleanly.
  backup_mu_path = os.path.join(wc_backup, 'A', 'mu')
  svntest.main.file_append(backup_mu_path, 'appended mu text')

  # Create unversioned files and dir that will obstruct A/B/F/nu, kappa,
  # A/C/I, and A/C/I/upsilon coming from repos during update.
  # The obstructing nu has the same contents as  the repos, while kappa and
  # upsilon differ, which means the latter two should show as modified after
  # the forced update.
  nu_path = os.path.join(wc_backup, 'A', 'B', 'F', 'nu')
  svntest.main.file_append(nu_path, "This is the file 'nu'\n")
  kappa_path = os.path.join(wc_backup, 'kappa')
  svntest.main.file_append(kappa_path,
                           "This is the OBSTRUCTING file 'kappa'\n")
  I_path = os.path.join(wc_backup, 'A', 'C', 'I')
  os.mkdir(I_path)
  upsilon_path = os.path.join(I_path, 'upsilon')
  svntest.main.file_append(upsilon_path,
                           "This is the OBSTRUCTING file 'upsilon'\n")

  # Create expected output tree for an update of the wc_backup.
  # mu and rho are run of the mill update operations; merge and update
  # respectively.
  # kappa, nu, I, and upsilon all 'E'xisted as unversioned items in the WC.
  # While the dir I does exist, zeta does not so it's just an add.
  expected_output = wc.State(wc_backup, {
    'A/mu'          : Item(status='G '),
    'A/D/G/rho'     : Item(status='U '),
    'kappa'         : Item(status='E '),
    'A/B/F/nu'      : Item(status='E '),
    'A/C/I'         : Item(status='E '),
    'A/C/I/upsilon' : Item(status='E '),
    'A/C/I/zeta'    : Item(status='A '),
    })

  # Create expected output tree for an update of the wc_backup.
  #
  # - mu and rho are run of the mill update operations; merge and update
  #   respectively.
  #
  # - kappa, nu, I, and upsilon all 'E'xisted as unversioned items in the WC.
  #
  # - While the dir I does exist, I/zeta does not so it's just an add.
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/B/F/nu'      : Item("This is the file 'nu'\n"),
    'kappa'         : Item("This is the OBSTRUCTING file 'kappa'\n"),
    'A/C/I'         : Item(),
    'A/C/I/upsilon' : Item("This is the OBSTRUCTING file 'upsilon'\n"),
    'A/C/I/zeta'    : Item("This is the file 'zeta'\n"),
    })
  expected_disk.tweak('A/mu',
                      contents=expected_disk.desc['A/mu'].contents
                      + 'appended mu text')
  expected_disk.tweak('A/D/G/rho',
                      contents=expected_disk.desc['A/D/G/rho'].contents
                      + 'new appended text for rho')

  # Create expected status tree for the update.  Since the obstructing
  # kappa and upsilon differ from the repos, they should show as modified.
  expected_status = svntest.actions.get_virginal_state(wc_backup, 2)
  expected_status.add({
    'A/B/F/nu'      : Item(status='  ', wc_rev=2),
    'A/C/I'         : Item(status='  ', wc_rev=2),
    'A/C/I/zeta'    : Item(status='  ', wc_rev=2),
    'kappa'         : Item(status='M ', wc_rev=2),
    'A/C/I/upsilon' : Item(status='M ', wc_rev=2),
    })

  # Perform forced update and check the results in three ways.
  svntest.actions.run_and_verify_update(wc_backup,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None, None, None, 0,
                                        wc_backup, '--force')

#----------------------------------------------------------------------
def forced_update_failures(sbox):
  "forced up fails with some types of obstructions"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Make a backup copy of the working copy
  wc_backup = sbox.add_wc_path('backup')
  svntest.actions.duplicate_dir(wc_dir, wc_backup)

  # Add a file
  nu_path = os.path.join(wc_dir, 'A', 'B', 'F', 'nu')
  svntest.main.file_append(nu_path, "This is the file 'nu'\n")
  svntest.main.run_svn(None, 'add', nu_path)

  # Add a dir
  I_path = os.path.join(wc_dir, 'A', 'C', 'I')
  os.mkdir(I_path)
  svntest.main.run_svn(None, 'add', I_path)

  # Created expected output tree for 'svn ci'
  expected_output = wc.State(wc_dir, {
    'A/B/F/nu'      : Item(verb='Adding'),
    'A/C/I'         : Item(verb='Adding'),
    })

  # Create expected status tree.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/B/F/nu'      : Item(status='  ', wc_rev=2),
    'A/C/I'         : Item(status='  ', wc_rev=2),
    })

  # Commit.
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Create an unversioned dir A/B/F/nu that will obstruct the file of the
  # same name coming from the repository.  Create an unversioned file A/C/I
  # that will obstruct the dir of the same name.
  nu_path = os.path.join(wc_backup, 'A', 'B', 'F', 'nu')
  os.mkdir(nu_path)
  I_path = os.path.join(wc_backup, 'A', 'C', 'I')
  svntest.main.file_append(I_path,
                           "This is the file 'I'...shouldn't I be a dir?\n")

  # A forced update that tries to add a file when an unversioned directory
  # of the same name already exists should fail.
  F_Path = os.path.join(wc_backup, 'A', 'B', 'F')
  svntest.actions.run_and_verify_update(F_Path, None, None, None,
                                        ".*Failed to add file.*" + \
                                        "a non-file object of the " + \
                                        "same name already exists",
                                        None, None, None, None, 0, F_Path,
                                        '--force')

  # A forced update that tries to add a directory when an unversioned file
  # of the same name already exists should fail.
  C_Path = os.path.join(wc_backup, 'A', 'C')
  svntest.actions.run_and_verify_update(C_Path, None, None, None,
                                        ".*Failed to add directory.*" + \
                                        "a non-directory object of the " + \
                                        "same name already exists",
                                        None, None, None, None, 0, C_Path,
                                        '--force')

  # Clean-up what we have done so far.  Remove the unversioned file A/C/I
  # and the unversioned directory A/B/F/nu.  Then update the backup to
  # r2, except for A/C, update that to r1 so A/C/I isn't present.
  # working copy.
  os.remove(I_path)
  os.rmdir(nu_path)
  svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput, [],
                                     'up', wc_backup)
  svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput, [],
                                     'up', '-r', '1', C_Path)

  # Checkout %URL%/A/C/I@2 directly to A/C/I.  A/C, being at r1, views
  # this as an unversioned object.
  I_url = sbox.repo_url + "/A/C/I"
  exit_code, so, se = svntest.actions.run_and_verify_svn(
    "Unexpected error during co",
    ['Checked out revision 2.\n'], [],
    "co", I_url, I_path)
  svntest.actions.run_and_verify_update(C_Path, None, None, None,
                               "Failed to add directory '.*I'.*already exists",
                                        None, None, None, None, 0, C_Path,
                                        '--force')

#----------------------------------------------------------------------
# Test for issue #2556. The tests maps a virtual drive to a working copy
# and tries some basic update, commit and status actions on the virtual
# drive.
def update_wc_on_windows_drive(sbox):
  "update wc on the root of a Windows (virtual) drive"

  def find_the_next_available_drive_letter():
    "find the first available drive"

    # get the list of used drive letters, use some Windows specific function.
    try:
      import win32api

      drives=win32api.GetLogicalDriveStrings()
      drives=drives.split('\000')

      for d in range(ord('G'), ord('Z')+1):
        drive = chr(d)
        if not drive + ':\\' in drives:
          return drive
    except ImportError:
      return None

    return None

  # just create an empty folder, we'll checkout later.
  sbox.build(create_wc = False)
  svntest.main.safe_rmtree(sbox.wc_dir)
  os.mkdir(sbox.wc_dir)

  # create a virtual drive to the working copy folder
  drive = find_the_next_available_drive_letter()
  if drive is None:
    raise svntest.Skip

  subprocess.call(['subst', drive +':', sbox.wc_dir])
  wc_dir = drive + ':/'
  was_cwd = os.getcwd()

  try:
    svntest.actions.run_and_verify_svn(None, None, [],
                                       'checkout',
                                       sbox.repo_url, wc_dir)

    # Make some local modifications
    mu_path = os.path.join(wc_dir, 'A', 'mu')
    svntest.main.file_append(mu_path, '\nAppended text for mu')
    zeta_path = os.path.join(wc_dir, 'zeta')
    svntest.main.file_append(zeta_path, "This is the file 'zeta'\n")
    svntest.main.run_svn(None, 'add', zeta_path)

    # Commit.
    expected_output = svntest.wc.State(wc_dir, {
      'A/mu' : Item(verb='Sending'),
      'zeta' : Item(verb='Adding'),
      })
    expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
    expected_status.tweak('A/mu', wc_rev=2)
    expected_status.add({
    'zeta' : Item(status='  ', wc_rev=2),
    })
    svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                          expected_status, None,
                                          wc_dir, zeta_path)

    # Non recursive commit
    dir1_path = os.path.join(wc_dir, 'dir1')
    os.mkdir(dir1_path)
    svntest.main.run_svn(None, 'add', '-N', dir1_path)
    file1_path = os.path.join(dir1_path, 'file1')
    svntest.main.file_append(file1_path, "This is the file 'file1'\n")
    svntest.main.run_svn(None, 'add', '-N', file1_path)

    expected_output = svntest.wc.State(wc_dir, {
      'dir1' : Item(verb='Adding'),
      'dir1/file1' : Item(verb='Adding'),
      })
    expected_status.add({
    'dir1' : Item(status='  ', wc_rev=3),
    'dir1/file1' : Item(status='  ', wc_rev=3),
    })
    svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                          expected_status, None,
                                          '-N',
                                          wc_dir,
                                          dir1_path, file1_path)

    # revert to previous revision to test update
    os.chdir(wc_dir)

    expected_disk = svntest.main.greek_state.copy()
    expected_output = svntest.wc.State(wc_dir, {
      'A/mu' : Item(status='U '),
      'zeta' : Item(status='D '),
      'dir1' : Item(status='D '),
      })
    expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
    svntest.actions.run_and_verify_update(wc_dir,
                                          expected_output,
                                          expected_disk,
                                          expected_status,
                                          None, None, None, None, None, 0,
                                          '-r', '1', wc_dir)

    os.chdir(was_cwd)

    # update to the latest version, but use the relative path 'X:'
    wc_dir = drive + ":"
    expected_output = svntest.wc.State(wc_dir, {
      'A/mu' : Item(status='U '),
      'zeta' : Item(status='A '),
      'dir1' : Item(status='A '),
      'dir1/file1' : Item(status='A '),
      })
    expected_status = svntest.actions.get_virginal_state(wc_dir, 3)
    expected_status.add({
      'dir1' : Item(status='  ', wc_rev=3),
      'dir1/file1' : Item(status='  ', wc_rev=3),
      'zeta' : Item(status='  ', wc_rev=3),
      })
    expected_disk.add({
      'zeta'    : Item("This is the file 'zeta'\n"),
      'dir1/file1': Item("This is the file 'file1'\n"),
      })
    expected_disk.tweak('A/mu', contents = expected_disk.desc['A/mu'].contents
                        + '\nAppended text for mu')
    svntest.actions.run_and_verify_update(wc_dir,
                                          expected_output,
                                          expected_disk,
                                          expected_status)

  finally:
    os.chdir(was_cwd)
    # cleanup the virtual drive
    subprocess.call(['subst', '/D', drive +':'])

# Issue #2618: "'Checksum mismatch' error when receiving
# update for replaced-with-history file".
def update_wc_with_replaced_file(sbox):
  "update wc containing a replaced-with-history file"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Make a backup copy of the working copy.
  wc_backup = sbox.add_wc_path('backup')
  svntest.actions.duplicate_dir(wc_dir, wc_backup)

  # we need a change in the repository
  iota_path = os.path.join(wc_dir, 'iota')
  mu_path = os.path.join(wc_dir, 'A', 'mu')
  iota_bu_path = os.path.join(wc_backup, 'iota')
  svntest.main.file_append(iota_bu_path, "New line in 'iota'\n")
  svntest.main.run_svn(None,
                       'ci', wc_backup, '-m', 'changed file')

  # First, a replacement without history.
  svntest.main.run_svn(None, 'rm', iota_path)
  svntest.main.file_append(iota_path, "")
  svntest.main.run_svn(None, 'add', iota_path)

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', status='R ', wc_rev='1')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Now update the wc.  The delete half of the local replacement
  # is a tree conflict with the incoming edit on that deleted item.
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(status='  ', treeconflict='C'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'iota' : Item(status='R ', wc_rev='2', treeconflict='C'),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('iota', contents="")
  conflict_files = []
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None,
                                        svntest.tree.detect_conflict_files,
                                        conflict_files)

  # Make us a working copy with a 'replace-with-history' file.
  svntest.main.run_svn(None, 'revert', iota_path)
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(status='U '),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_disk = svntest.main.greek_state.copy()
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None,
                                        None, None, None, None, 0,
                                        wc_dir, '-r1')

  svntest.main.run_svn(None, 'rm', iota_path)
  svntest.main.run_svn(None, 'cp', mu_path, iota_path)

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', status='R ', copied='+', wc_rev='-')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Now update the wc.  The delete half of the local replacement
  # is a tree conflict with the incoming edit on that deleted item.
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(status='  ', treeconflict='C'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'iota' : Item(status='R ', wc_rev='-', treeconflict='C', copied='+'),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('iota', contents="This is the file 'mu'.\n")
  conflict_files = [ ]
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None,
                                        svntest.tree.detect_conflict_files,
                                        conflict_files)

#----------------------------------------------------------------------
def update_with_obstructing_additions(sbox):
  "update handles obstructing paths scheduled for add"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Make a backup copy of the working copy
  wc_backup = sbox.add_wc_path('backup')
  svntest.actions.duplicate_dir(wc_dir, wc_backup)

  # Add files and dirs to the repos via the first WC.  Each of these
  # will be added to the backup WC via an update:
  #
  #  A/B/upsilon:   Identical to the file scheduled for addition in
  #                 the backup WC.
  #
  #  A/C/nu:        A "normal" add, won't exist in the backup WC.
  #
  #  A/D/kappa:     Textual and property conflict with the file scheduled
  #                 for addition in the backup WC.
  #
  #  A/D/epsilon:   Textual conflict with the file scheduled for addition.
  #
  #  A/D/zeta:      Prop conflict with the file scheduled for addition.
  #
  #                 Three new dirs that will also be scheduled for addition:
  #  A/D/H/I:         No props on either WC or REPOS.
  #  A/D/H/I/J:       Prop conflict with the scheduled add.
  #  A/D/H/I/K:       Same (mergeable) prop on WC and REPOS.
  #
  #  A/D/H/I/K/xi:  Identical to the file scheduled for addition in
  #                 the backup WC. No props.
  #
  #  A/D/H/I/L:     A "normal" dir add, won't exist in the backup WC.
  #
  #  A/D/H/I/J/eta: Conflicts with the file scheduled for addition in
  #                 the backup WC.  No props.
  upsilon_path = os.path.join(wc_dir, 'A', 'B', 'upsilon')
  svntest.main.file_append(upsilon_path, "This is the file 'upsilon'\n")
  nu_path = os.path.join(wc_dir, 'A', 'C', 'nu')
  svntest.main.file_append(nu_path, "This is the file 'nu'\n")
  kappa_path = os.path.join(wc_dir, 'A', 'D', 'kappa')
  svntest.main.file_append(kappa_path, "This is REPOS file 'kappa'\n")
  epsilon_path = os.path.join(wc_dir, 'A', 'D', 'epsilon')
  svntest.main.file_append(epsilon_path, "This is REPOS file 'epsilon'\n")
  zeta_path = os.path.join(wc_dir, 'A', 'D', 'zeta')
  svntest.main.file_append(zeta_path, "This is the file 'zeta'\n")
  I_path = os.path.join(wc_dir, 'A', 'D', 'H', 'I')
  os.mkdir(I_path)
  J_path = os.path.join(I_path, 'J')
  os.mkdir(J_path)
  K_path = os.path.join(I_path, 'K')
  os.mkdir(K_path)
  L_path = os.path.join(I_path, 'L')
  os.mkdir(L_path)
  xi_path = os.path.join(K_path, 'xi')
  svntest.main.file_append(xi_path, "This is the file 'xi'\n")
  eta_path = os.path.join(J_path, 'eta')
  svntest.main.file_append(eta_path, "This is REPOS file 'eta'\n")
  svntest.main.run_svn(None, 'add', upsilon_path, nu_path,
                       kappa_path, epsilon_path, zeta_path, I_path)

  # Set props that will conflict with scheduled adds.
  svntest.main.run_svn(None, 'propset', 'propname1', 'propval-REPOS',
                       kappa_path)
  svntest.main.run_svn(None, 'propset', 'propname1', 'propval-REPOS',
                       zeta_path)
  svntest.main.run_svn(None, 'propset', 'propname1', 'propval-REPOS',
                       J_path)

  # Set prop that will match with scheduled add.
  svntest.main.run_svn(None, 'propset', 'propname1', 'propval-SAME',
                       epsilon_path)
  svntest.main.run_svn(None, 'propset', 'propname1', 'propval-SAME',
                       K_path)

  # Created expected output tree for 'svn ci'
  expected_output = wc.State(wc_dir, {
    'A/B/upsilon'   : Item(verb='Adding'),
    'A/C/nu'        : Item(verb='Adding'),
    'A/D/kappa'     : Item(verb='Adding'),
    'A/D/epsilon'   : Item(verb='Adding'),
    'A/D/zeta'      : Item(verb='Adding'),
    'A/D/H/I'       : Item(verb='Adding'),
    'A/D/H/I/J'     : Item(verb='Adding'),
    'A/D/H/I/J/eta' : Item(verb='Adding'),
    'A/D/H/I/K'     : Item(verb='Adding'),
    'A/D/H/I/K/xi'  : Item(verb='Adding'),
    'A/D/H/I/L'     : Item(verb='Adding'),
    })

  # Create expected status tree.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/B/upsilon'   : Item(status='  ', wc_rev=2),
    'A/C/nu'        : Item(status='  ', wc_rev=2),
    'A/D/kappa'     : Item(status='  ', wc_rev=2),
    'A/D/epsilon'   : Item(status='  ', wc_rev=2),
    'A/D/zeta'      : Item(status='  ', wc_rev=2),
    'A/D/H/I'       : Item(status='  ', wc_rev=2),
    'A/D/H/I/J'     : Item(status='  ', wc_rev=2),
    'A/D/H/I/J/eta' : Item(status='  ', wc_rev=2),
    'A/D/H/I/K'     : Item(status='  ', wc_rev=2),
    'A/D/H/I/K/xi'  : Item(status='  ', wc_rev=2),
    'A/D/H/I/L'     : Item(status='  ', wc_rev=2),
    })

  # Commit.
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Create various paths scheduled for addition which will obstruct
  # the adds coming from the repos.
  upsilon_backup_path = os.path.join(wc_backup, 'A', 'B', 'upsilon')
  svntest.main.file_append(upsilon_backup_path,
                           "This is the file 'upsilon'\n")
  kappa_backup_path = os.path.join(wc_backup, 'A', 'D', 'kappa')
  svntest.main.file_append(kappa_backup_path,
                           "This is WC file 'kappa'\n")
  epsilon_backup_path = os.path.join(wc_backup, 'A', 'D', 'epsilon')
  svntest.main.file_append(epsilon_backup_path,
                           "This is WC file 'epsilon'\n")
  zeta_backup_path = os.path.join(wc_backup, 'A', 'D', 'zeta')
  svntest.main.file_append(zeta_backup_path, "This is the file 'zeta'\n")
  I_backup_path = os.path.join(wc_backup, 'A', 'D', 'H', 'I')
  os.mkdir(I_backup_path)
  J_backup_path = os.path.join(I_backup_path, 'J')
  os.mkdir(J_backup_path)
  K_backup_path = os.path.join(I_backup_path, 'K')
  os.mkdir(K_backup_path)
  xi_backup_path = os.path.join(K_backup_path, 'xi')
  svntest.main.file_append(xi_backup_path, "This is the file 'xi'\n")
  eta_backup_path = os.path.join(J_backup_path, 'eta')
  svntest.main.file_append(eta_backup_path, "This is WC file 'eta'\n")

  svntest.main.run_svn(None, 'add', upsilon_backup_path, kappa_backup_path,
                       epsilon_backup_path, zeta_backup_path, I_backup_path)

  # Set prop that will conflict with add from repos.
  svntest.main.run_svn(None, 'propset', 'propname1', 'propval-WC',
                       kappa_backup_path)
  svntest.main.run_svn(None, 'propset', 'propname1', 'propval-WC',
                       zeta_backup_path)
  svntest.main.run_svn(None, 'propset', 'propname1', 'propval-WC',
                       J_backup_path)

  # Set prop that will match add from repos.
  svntest.main.run_svn(None, 'propset', 'propname1', 'propval-SAME',
                       epsilon_backup_path)
  svntest.main.run_svn(None, 'propset', 'propname1', 'propval-SAME',
                       K_backup_path)

  # Create expected output tree for an update of the wc_backup.
  expected_output = wc.State(wc_backup, {
    'A/B/upsilon'   : Item(status='E '),
    'A/C/nu'        : Item(status='A '),
    'A/D/H/I'       : Item(status='E '),
    'A/D/H/I/J'     : Item(status='EC'),
    'A/D/H/I/J/eta' : Item(status='C '),
    'A/D/H/I/K'     : Item(status='EG'),
    'A/D/H/I/K/xi'  : Item(status='E '),
    'A/D/H/I/L'     : Item(status='A '),
    'A/D/kappa'     : Item(status='CC'),
    'A/D/epsilon'   : Item(status='CG'),
    'A/D/zeta'      : Item(status='EC'),
    })

  # Create expected disk for update of wc_backup.
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/B/upsilon'   : Item("This is the file 'upsilon'\n"),
    'A/C/nu'        : Item("This is the file 'nu'\n"),
    'A/D/H/I'       : Item(),
    'A/D/H/I/J'     : Item(props={'propname1' : 'propval-WC'}),
    'A/D/H/I/J/eta' : Item("\n".join(["<<<<<<< .mine",
                                      "This is WC file 'eta'",
                                      "=======",
                                      "This is REPOS file 'eta'",
                                      ">>>>>>> .r2",
                                      ""])),
    'A/D/H/I/K'     : Item(props={'propname1' : 'propval-SAME'}),
    'A/D/H/I/K/xi'  : Item("This is the file 'xi'\n"),
    'A/D/H/I/L'     : Item(),
    'A/D/kappa'     : Item("\n".join(["<<<<<<< .mine",
                                      "This is WC file 'kappa'",
                                      "=======",
                                      "This is REPOS file 'kappa'",
                                      ">>>>>>> .r2",
                                      ""]),
                           props={'propname1' : 'propval-WC'}),
    'A/D/epsilon'     : Item("\n".join(["<<<<<<< .mine",
                                        "This is WC file 'epsilon'",
                                        "=======",
                                        "This is REPOS file 'epsilon'",
                                        ">>>>>>> .r2",
                                        ""]),
                             props={'propname1' : 'propval-SAME'}),
    'A/D/zeta'   : Item("This is the file 'zeta'\n",
                        props={'propname1' : 'propval-WC'}),
    })

  # Create expected status tree for the update.  Since the obstructing
  # kappa and upsilon differ from the repos, they should show as modified.
  expected_status = svntest.actions.get_virginal_state(wc_backup, 2)
  expected_status.add({
    'A/B/upsilon'   : Item(status='  ', wc_rev=2),
    'A/C/nu'        : Item(status='  ', wc_rev=2),
    'A/D/H/I'       : Item(status='  ', wc_rev=2),
    'A/D/H/I/J'     : Item(status=' C', wc_rev=2),
    'A/D/H/I/J/eta' : Item(status='C ', wc_rev=2),
    'A/D/H/I/K'     : Item(status='  ', wc_rev=2),
    'A/D/H/I/K/xi'  : Item(status='  ', wc_rev=2),
    'A/D/H/I/L'     : Item(status='  ', wc_rev=2),
    'A/D/kappa'     : Item(status='CC', wc_rev=2),
    'A/D/epsilon'   : Item(status='C ', wc_rev=2),
    'A/D/zeta'      : Item(status=' C', wc_rev=2),
    })

  # "Extra" files that we expect to result from the conflicts.
  extra_files = ['eta\.r0', 'eta\.r2', 'eta\.mine',
                 'kappa\.r0', 'kappa\.r2', 'kappa\.mine',
                 'epsilon\.r0', 'epsilon\.r2', 'epsilon\.mine',
                 'kappa.prej', 'zeta.prej', 'dir_conflicts.prej']

  # Perform forced update and check the results in three
  # ways (including props).
  svntest.actions.run_and_verify_update(wc_backup,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None,
                                        svntest.tree.detect_conflict_files,
                                        extra_files, None, None, 1,
                                        wc_backup)

  # Some obstructions are still not permitted:
  #
  # Test that file and dir obstructions scheduled for addition *with*
  # history fail when update tries to add the same path.

  # URL to URL copy of A/D/G to A/M.
  G_URL = sbox.repo_url + '/A/D/G'
  M_URL = sbox.repo_url + '/A/M'
  svntest.actions.run_and_verify_svn("Copy error:", None, [],
                                     'cp', G_URL, M_URL, '-m', '')

  # WC to WC copy of A/D/H to A/M, M now scheduled for addition with
  # history in WC and pending addition from the repos.
  H_path = os.path.join(wc_dir, 'A', 'D', 'H')
  A_path = os.path.join(wc_dir, 'A')
  M_path = os.path.join(wc_dir, 'A', 'M')

  svntest.actions.run_and_verify_svn("Copy error:", None, [],
                                     'cp', H_path, M_path)

  # URL to URL copy of A/D/H/omega to omicron.
  omega_URL = sbox.repo_url + '/A/D/H/omega'
  omicron_URL = sbox.repo_url + '/omicron'
  svntest.actions.run_and_verify_svn("Copy error:", None, [],
                                     'cp', omega_URL, omicron_URL,
                                     '-m', '')

  # WC to WC copy of A/D/H/chi to omicron, omicron now scheduled for
  # addition with history in WC and pending addition from the repos.
  chi_path = os.path.join(wc_dir, 'A', 'D', 'H', 'chi')
  omicron_path = os.path.join(wc_dir, 'omicron')

  svntest.actions.run_and_verify_svn("Copy error:", None, [],
                                     'cp', chi_path,
                                     omicron_path)

  # Try to update M's Parent.
  expected_output = wc.State(A_path, {
    'M'   : Item(status='  ', treeconflict='C'),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/B/upsilon'   : Item("This is the file 'upsilon'\n"),
    'A/C/nu'        : Item("This is the file 'nu'\n"),
    'A/D/H/I'       : Item(),
    'A/D/H/I/J'     : Item(),
    'A/D/H/I/J/eta' : Item("This is REPOS file 'eta'\n"),
    'A/D/H/I/K'     : Item(),
    'A/D/H/I/K/xi'  : Item("This is the file 'xi'\n"),
    'A/D/H/I/L'     : Item(),
    'A/D/kappa'     : Item("This is REPOS file 'kappa'\n"),
    'A/D/epsilon'   : Item("This is REPOS file 'epsilon'\n"),
    'A/D/gamma'     : Item("This is the file 'gamma'.\n"),
    'A/D/zeta'      : Item("This is the file 'zeta'\n"),
    'A/M/I'         : Item(),
    'A/M/I/J'       : Item(),
    'A/M/I/J/eta'   : Item("This is REPOS file 'eta'\n"),
    'A/M/I/K'       : Item(),
    'A/M/I/K/xi'    : Item("This is the file 'xi'\n"),
    'A/M/I/L'       : Item(),
    'A/M/chi'       : Item("This is the file 'chi'.\n"),
    'A/M/psi'       : Item("This is the file 'psi'.\n"),
    'A/M/omega'     : Item("This is the file 'omega'.\n"),
    'omicron'       : Item("This is the file 'chi'.\n"),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 4)
  expected_status.tweak('', 'iota', wc_rev=1)
  expected_status.add({
    'A/B/upsilon'   : Item(status='  ', wc_rev=4),
    'A/C/nu'        : Item(status='  ', wc_rev=4),
    'A/D/kappa'     : Item(status='  ', wc_rev=4),
    'A/D/epsilon'   : Item(status='  ', wc_rev=4),
    'A/D/gamma'     : Item(status='  ', wc_rev=4),
    'A/D/zeta'      : Item(status='  ', wc_rev=4),
    'A/D/H/I'       : Item(status='  ', wc_rev=4),
    'A/D/H/I/J'     : Item(status='  ', wc_rev=4),
    'A/D/H/I/J/eta' : Item(status='  ', wc_rev=4),
    'A/D/H/I/K'     : Item(status='  ', wc_rev=4),
    'A/D/H/I/K/xi'  : Item(status='  ', wc_rev=4),
    'A/D/H/I/L'     : Item(status='  ', wc_rev=4),
    'A/M'           : Item(status='A ', copied='+', wc_rev='-',
                           treeconflict='C'),
    'A/M/I'         : Item(status='  ', copied='+', wc_rev='-'),
    'A/M/I/J'       : Item(status='  ', copied='+', wc_rev='-'),
    'A/M/I/J/eta'   : Item(status='  ', copied='+', wc_rev='-'),
    'A/M/I/K'       : Item(status='  ', copied='+', wc_rev='-'),
    'A/M/I/K/xi'    : Item(status='  ', copied='+', wc_rev='-'),
    'A/M/I/L'       : Item(status='  ', copied='+', wc_rev='-'),
    'A/M/chi'       : Item(status='  ', copied='+', wc_rev='-'),
    'A/M/psi'       : Item(status='  ', copied='+', wc_rev='-'),
    'A/M/omega'     : Item(status='  ', copied='+', wc_rev='-'),
    'omicron'       : Item(status='A ', copied='+', wc_rev='-'),
    })
  svntest.actions.run_and_verify_update(wc_dir, expected_output,
                                        expected_disk, expected_status,
                                        None, None, None, None, None, False,
                                        A_path)
  # Resolve the tree conflict.
  svntest.main.run_svn(None, 'resolve', '--accept', 'working', M_path)

  # --force shouldn't help either.
  svntest.actions.run_and_verify_update(wc_dir, expected_output,
                                        expected_disk, expected_status,
                                        None, None, None, None, None, False,
                                        M_path, '--force')

  # Try to update omicron's parent, non-recusively so as not to
  # try and update M first.
  expected_output = wc.State(wc_dir, {
    'omicron'   : Item(status='  ', treeconflict='C'),
    })
  expected_status.tweak('', 'iota', status='  ', wc_rev=4)
  expected_status.tweak('omicron', status='A ', copied='+', wc_rev='-',
                        treeconflict='C'),
  svntest.actions.run_and_verify_update(wc_dir, expected_output,
                                        expected_disk, expected_status,
                                        None, None, None, None, None, False,
                                        wc_dir, '-N')
  # Resolve the tree conflict.
  svntest.main.run_svn(None, 'resolve', '--accept', 'working', omicron_path)

  # Again, --force shouldn't matter.
  svntest.actions.run_and_verify_update(wc_dir, expected_output,
                                        expected_disk, expected_status,
                                        None, None, None, None, None, False,
                                        omicron_path, '-N', '--force')

# Test for issue #2022: Update shouldn't touch conflicted files.
def update_conflicted(sbox):
  "update conflicted files"
  sbox.build()
  wc_dir = sbox.wc_dir
  iota_path = os.path.join(wc_dir, 'iota')
  lambda_path = os.path.join(wc_dir, 'A', 'B', 'lambda')
  mu_path = os.path.join(wc_dir, 'A', 'mu')
  D_path = os.path.join(wc_dir, 'A', 'D')
  pi_path = os.path.join(wc_dir, 'A', 'D', 'G', 'pi')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)

  # Make some modifications to the files and a dir, creating r2.
  svntest.main.file_append(iota_path, 'Original appended text for iota\n')
  svntest.main.run_svn(None, 'propset', 'prop', 'val', lambda_path)
  svntest.main.file_append(mu_path, 'Original appended text for mu\n')
  svntest.main.run_svn(None, 'propset', 'prop', 'val', mu_path)
  svntest.main.run_svn(None, 'propset', 'prop', 'val', D_path)
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(verb='Sending'),
    'A/mu': Item(verb='Sending'),
    'A/B/lambda': Item(verb='Sending'),
    'A/D': Item(verb='Sending'),
    })

  expected_status.tweak('iota', 'A/mu', 'A/B/lambda', 'A/D', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Do another change to each path that we will need later.
  # Also, change a file below A/D in the path.
  svntest.main.file_append(iota_path, 'Another line for iota\n')
  svntest.main.file_append(mu_path, 'Another line for mu\n')
  svntest.main.file_append(lambda_path, 'Another line for lambda\n')
  svntest.main.run_svn(None, 'propset', 'prop', 'val2', D_path)
  svntest.main.file_append(pi_path, 'Another line for pi\n')
  expected_status.tweak('iota', 'A/mu', 'A/B/lambda', 'A/D', 'A/D/G/pi',
                        wc_rev=3)
  expected_output.add({
    'A/D/G/pi': Item(verb='Sending')})
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Go back to revision 1.
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(status='U '),
    'A/B/lambda' : Item(status='UU'),
    'A/mu' : Item(status='UU'),
    'A/D': Item(status=' U'),
    'A/D/G/pi': Item(status='U '),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None,
                                        None, None,
                                        None, None, 1,
                                        '-r1', wc_dir)

  # Create modifications conflicting with rev 2.
  svntest.main.file_append(iota_path, 'Conflicting appended text for iota\n')
  svntest.main.run_svn(None, 'propset', 'prop', 'conflictval', lambda_path)
  svntest.main.file_append(mu_path, 'Conflicting appended text for mu\n')
  svntest.main.run_svn(None, 'propset', 'prop', 'conflictval', mu_path)
  svntest.main.run_svn(None, 'propset', 'prop', 'conflictval', D_path)

  # Update to revision 2, expecting conflicts.
  expected_output = svntest.wc.State(wc_dir, {
    'iota': Item(status='C '),
    'A/B/lambda': Item(status=' C'),
    'A/mu': Item(status='CC'),
    'A/D': Item(status=' C'),
    })
  expected_disk.tweak('iota',
                      contents="\n".join(["This is the file 'iota'.",
                                          "<<<<<<< .mine",
                                          "Conflicting appended text for iota",
                                          "=======",
                                          "Original appended text for iota",
                                          ">>>>>>> .r2",
                                          ""]))
  expected_disk.tweak('A/mu',
                      contents="\n".join(["This is the file 'mu'.",
                                          "<<<<<<< .mine",
                                          "Conflicting appended text for mu",
                                          "=======",
                                          "Original appended text for mu",
                                          ">>>>>>> .r2",
                                          ""]),
                      props={'prop': 'conflictval'})
  expected_disk.tweak('A/B/lambda', 'A/D', props={'prop': 'conflictval'})

  expected_status.tweak(wc_rev=2)
  expected_status.tweak('iota', status='C ')
  expected_status.tweak('A/B/lambda', 'A/D', status=' C')
  expected_status.tweak('A/mu', status='CC')
  extra_files = [ [wc_dir, 'iota.*\.(r1|r2|mine)'],
                  [wc_dir, 'mu.*\.(r1|r2|mine|prej)'],
                  [wc_dir, 'lambda.*\.prej'],
                  [wc_dir, 'dir_conflicts.prej']]
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None,
                                        detect_extra_files, extra_files,
                                        None, None, 1,
                                        '-r2', wc_dir)

  # Now, update to HEAD, which should skip all the conflicted files, but
  # still update the pi file.
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(verb='Skipped'),
    'A/B/lambda' : Item(verb='Skipped'),
    'A/mu' : Item(verb='Skipped'),
    'A/D' : Item(verb='Skipped'),
    })
  expected_status.tweak(wc_rev=3)
  expected_status.tweak('iota', 'A/B/lambda', 'A/mu', 'A/D', wc_rev=2)
  # We no longer update descendants of a prop-conflicted dir.
  expected_status.tweak('A/D/G',
                        'A/D/G/pi',
                        'A/D/G/rho',
                        'A/D/G/tau',
                        'A/D/H',
                        'A/D/H/chi',
                        'A/D/H/omega',
                        'A/D/H/psi',
                        'A/D/gamma', wc_rev=2)

  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None,
                                        detect_extra_files, extra_files,
                                        None, None, 1)

#----------------------------------------------------------------------
def mergeinfo_update_elision(sbox):
  "mergeinfo does not elide after update"

  # No mergeinfo elision is performed when doing updates.  So updates may
  # result in equivalent mergeinfo on a path and it's nearest working copy
  # parent with explicit mergeinfo.  This is currently permitted and
  # honestly we could probably do without this test(?).

  sbox.build()
  wc_dir = sbox.wc_dir

  # Some paths we'll care about
  alpha_COPY_path = os.path.join(wc_dir, "A", "B_COPY", "E", "alpha")
  alpha_path  = os.path.join(wc_dir, "A", "B", "E", "alpha")
  B_COPY_path = os.path.join(wc_dir, "A", "B_COPY")
  E_COPY_path = os.path.join(wc_dir, "A", "B_COPY", "E")
  beta_path   = os.path.join(wc_dir, "A", "B", "E", "beta")
  lambda_path = os.path.join(wc_dir, "A", "B", "lambda")

  # Make a branch A/B_COPY
  svntest.actions.run_and_verify_svn(
    None,
    ["A    " + os.path.join(wc_dir, "A", "B_COPY", "lambda") + "\n",
     "A    " + os.path.join(wc_dir, "A", "B_COPY", "E") + "\n",
     "A    " + os.path.join(wc_dir, "A", "B_COPY", "E", "alpha") + "\n",
     "A    " + os.path.join(wc_dir, "A", "B_COPY", "E", "beta") + "\n",
     "A    " + os.path.join(wc_dir, "A", "B_COPY", "F") + "\n",
     "Checked out revision 1.\n",
     "A         " + B_COPY_path + "\n"],
    [],
    'copy',
    sbox.repo_url + "/A/B",
    B_COPY_path)

  expected_output = wc.State(wc_dir, {'A/B_COPY' : Item(verb='Adding')})
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    "A/B_COPY"         : Item(status='  ', wc_rev=2),
    "A/B_COPY/lambda"  : Item(status='  ', wc_rev=2),
    "A/B_COPY/E"       : Item(status='  ', wc_rev=2),
    "A/B_COPY/E/alpha" : Item(status='  ', wc_rev=2),
    "A/B_COPY/E/beta"  : Item(status='  ', wc_rev=2),
    "A/B_COPY/F"       : Item(status='  ', wc_rev=2),})

  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        wc_dir)

  # Make some changes under A/B

  # r3 - modify and commit A/B/E/beta
  svntest.main.file_write(beta_path, "New content")
  expected_output = wc.State(wc_dir, {'A/B/E/beta' : Item(verb='Sending')})
  expected_status.tweak('A/B/E/beta', wc_rev=3)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # r4 - modify and commit A/B/lambda
  svntest.main.file_write(lambda_path, "New content")
  expected_output = wc.State(wc_dir, {'A/B/lambda' : Item(verb='Sending')})
  expected_status.tweak('A/B/lambda', wc_rev=4)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # r5 - modify and commit A/B/E/alpha
  svntest.main.file_write(alpha_path, "New content")
  expected_output = wc.State(wc_dir, {'A/B/E/alpha' : Item(verb='Sending')})
  expected_status.tweak('A/B/E/alpha', wc_rev=5)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Merge r2:5 into A/B_COPY
  expected_output = wc.State(B_COPY_path, {
    'lambda'  : Item(status='U '),
    'E/alpha' : Item(status='U '),
    'E/beta'  : Item(status='U '),
    })
  expected_merge_status = wc.State(B_COPY_path, {
    ''        : Item(status=' M', wc_rev=2),
    'lambda'  : Item(status='M ', wc_rev=2),
    'E'       : Item(status='  ', wc_rev=2),
    'E/alpha' : Item(status='M ', wc_rev=2),
    'E/beta'  : Item(status='M ', wc_rev=2),
    'F'       : Item(status='  ', wc_rev=2),
    })
  expected_merge_disk = wc.State('', {
    ''        : Item(props={SVN_PROP_MERGEINFO : '/A/B:3-5'}),
    'lambda'  : Item("New content"),
    'E'       : Item(),
    'E/alpha' : Item("New content"),
    'E/beta'  : Item("New content"),
    'F'       : Item(),
    })
  expected_skip = wc.State(B_COPY_path, { })

  svntest.actions.run_and_verify_merge(B_COPY_path, '2', '5',
                                       sbox.repo_url + \
                                       '/A/B',
                                       expected_output,
                                       expected_merge_disk,
                                       expected_merge_status,
                                       expected_skip,
                                       None, None, None, None,
                                       None, 1)

  # r6 - Commit the merge
  expected_output = wc.State(wc_dir,
                             {'A/B_COPY'         : Item(verb='Sending'),
                              'A/B_COPY/E/alpha' : Item(verb='Sending'),
                              'A/B_COPY/E/beta'  : Item(verb='Sending'),
                              'A/B_COPY/lambda'  : Item(verb='Sending')})
  expected_status.tweak('A/B_COPY',         wc_rev=6)
  expected_status.tweak('A/B_COPY/E/alpha', wc_rev=6)
  expected_status.tweak('A/B_COPY/E/beta',  wc_rev=6)
  expected_status.tweak('A/B_COPY/lambda',  wc_rev=6)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Update WC back to r5, A/COPY_B is at it's pre-merge state again
  expected_output = wc.State(wc_dir,
                             {'A/B_COPY'         : Item(status=' U'),
                              'A/B_COPY/E/alpha' : Item(status='U '),
                              'A/B_COPY/E/beta'  : Item(status='U '),
                              'A/B_COPY/lambda'  : Item(status='U '),})
  expected_status.tweak(wc_rev=5)
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/B_COPY'         : Item(),
    'A/B_COPY/lambda'  : Item("This is the file 'lambda'.\n"),
    'A/B_COPY/E'       : Item(),
    'A/B_COPY/E/alpha' : Item("This is the file 'alpha'.\n"),
    'A/B_COPY/E/beta'  : Item("This is the file 'beta'.\n"),
    'A/B_COPY/F'       : Item(),
    })
  expected_disk.tweak('A/B/lambda',  contents="New content")
  expected_disk.tweak('A/B/E/alpha', contents="New content")
  expected_disk.tweak('A/B/E/beta',  contents="New content")
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None,
                                        None, None, 1,
                                        '-r', '5', wc_dir)

  # Merge r2:5 to A/B_COPY/E/alpha
  expected_output = wc.State(alpha_COPY_path, {
    'alpha' : Item(status='U '),
    })
  expected_skip = wc.State(alpha_COPY_path, { })

  # run_and_verify_merge doesn't support merging to a file WCPATH
  # so use run_and_verify_svn.
  update_line = 'U    ' + alpha_COPY_path + '\n'
  if sys.platform == 'win32':
    # Construct a properly escaped regex when dealing with
    # '\' riddled paths on Windows.
    update_line = update_line.replace("\\", "\\\\")
  notify_line = svntest.main.merge_notify_line(3, 5, True, False)
  svntest.actions.run_and_verify_svn(None,
                                     '|'.join([notify_line, update_line]),
                                     [], 'merge', '-r2:5',
                                     sbox.repo_url + '/A/B/E/alpha',
                                     alpha_COPY_path)

  expected_alpha_status = wc.State(alpha_COPY_path, {
    ''        : Item(status='MM', wc_rev=5),
    })
  svntest.actions.run_and_verify_status(alpha_COPY_path,
                                        expected_alpha_status)

  svntest.actions.run_and_verify_svn(None, ["/A/B/E/alpha:3-5\n"], [],
                                     'propget', SVN_PROP_MERGEINFO,
                                     alpha_COPY_path)

  # Update WC.  The local mergeinfo (r3-5) on A/B_COPY/E/alpha is
  # identical to that on added to A/B_COPY by the update, but update
  # doesn't support elision so this redundancy is permitted.
  expected_output = wc.State(wc_dir, {
    'A/B_COPY/lambda'  : Item(status='U '),
    'A/B_COPY/E/alpha' : Item(status='G '),
    'A/B_COPY/E/beta'  : Item(status='U '),
    'A/B_COPY'         : Item(status=' U'),
    })
  expected_disk.tweak('A/B_COPY', props={SVN_PROP_MERGEINFO : '/A/B:3-5'})
  expected_disk.tweak('A/B_COPY/lambda', contents="New content")
  expected_disk.tweak('A/B_COPY/E/beta', contents="New content")
  expected_disk.tweak('A/B_COPY/E/alpha', contents="New content",
                      props={SVN_PROP_MERGEINFO : '/A/B/E/alpha:3-5'})
  expected_status.tweak(wc_rev=6)
  expected_status.tweak('A/B_COPY/E/alpha', status=' M')
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None,
                                        None, None, 1)

  # Now test that an updated target's mergeinfo can itself elide.
  # r7 - modify and commit A/B/E/alpha
  svntest.main.file_write(alpha_path, "More new content")
  expected_output = wc.State(wc_dir, {
    'A/B/E/alpha' : Item(verb='Sending'),
    'A/B_COPY/E/alpha' : Item(verb='Sending')})
  expected_status.tweak('A/B/E/alpha', 'A/B_COPY/E/alpha', status='  ',
                        wc_rev=7)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Update A to get all paths to the same working revision.
  svntest.actions.run_and_verify_svn(None, ["At revision 7.\n"], [],
                                     'up', wc_dir)

  # Merge r6:7 into A/B_COPY/E
  expected_output = wc.State(E_COPY_path, {
    'alpha' : Item(status='U '),
    })
  expected_merge_status = wc.State(E_COPY_path, {
    ''        : Item(status=' M', wc_rev=7),
    'alpha' : Item(status='MM', wc_rev=7),
    'beta'  : Item(status='  ', wc_rev=7),
    })
  expected_merge_disk = wc.State('', {
    ''        : Item(props={SVN_PROP_MERGEINFO : '/A/B/E:3-5,7'}),
    'alpha' : Item("More new content"),
    'beta'  : Item("New content"),
    })
  expected_skip = wc.State(E_COPY_path, { })

  svntest.actions.run_and_verify_merge(E_COPY_path, '6', '7',
                                       sbox.repo_url + \
                                       '/A/B/E',
                                       expected_output,
                                       expected_merge_disk,
                                       expected_merge_status,
                                       expected_skip,
                                       None, None, None, None,
                                       None, 1)

  # r8 - Commit the merge
  svntest.actions.run_and_verify_svn(None,
                                     ["At revision 7.\n"],
                                     [], 'update', wc_dir)
  expected_output = wc.State(wc_dir,
                             {'A/B_COPY/E'       : Item(verb='Sending'),
                              'A/B_COPY/E/alpha' : Item(verb='Sending')})
  expected_status.tweak(wc_rev=7)
  expected_status.tweak('A/B_COPY/E', 'A/B_COPY/E/alpha', wc_rev=8)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Update A/COPY_B/E back to r7
  expected_output = wc.State(wc_dir, {
    'A/B_COPY/E/alpha' : Item(status='UU'),
    'A/B_COPY/E'       : Item(status=' U'),
    })
  expected_status.tweak(wc_rev=7)
  expected_disk.tweak('A/B_COPY',
                      props={SVN_PROP_MERGEINFO : '/A/B:3-5'})
  expected_disk.tweak('A/B/E/alpha', contents="More new content")
  expected_disk.tweak('A/B_COPY/E/alpha', contents="New content")

  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None,
                                        None, None, 1,
                                        '-r', '7', E_COPY_path)

  # Merge r6:7 to A/B_COPY
  expected_output = wc.State(B_COPY_path, {
    'E/alpha' : Item(status='U '),
    })
  expected_merge_status = wc.State(B_COPY_path, {
    ''        : Item(status=' M', wc_rev=7),
    'lambda'  : Item(status='  ', wc_rev=7),
    'E'       : Item(status='  ', wc_rev=7),
    'E/alpha' : Item(status='MM', wc_rev=7),
    'E/beta'  : Item(status='  ', wc_rev=7),
    'F'       : Item(status='  ', wc_rev=7),
    })
  expected_merge_disk = wc.State('', {
    ''        : Item(props={SVN_PROP_MERGEINFO : '/A/B:3-5,7'}),
    'lambda'  : Item("New content"),
    'E'       : Item(),
    'E/alpha' : Item("More new content"),
    'E/beta'  : Item("New content"),
    'F'       : Item(),
    })
  expected_skip = wc.State(B_COPY_path, { })

  svntest.actions.run_and_verify_merge(B_COPY_path, '6', '7',
                                       sbox.repo_url + \
                                       '/A/B',
                                       expected_output,
                                       expected_merge_disk,
                                       expected_merge_status,
                                       expected_skip,
                                       None, None, None, None,
                                       None, 1,alpha_COPY_path)

  # Update just A/B_COPY/E.  The mergeinfo (r3-5,7) reset on
  # A/B_COPY/E by the udpate is identical to the local info on
  # A/B_COPY, so should elide, leaving no mereginfo on E.
  #expected_output = svntest.wc.State(wc_dir, { })
  expected_output = wc.State(wc_dir, {
    'A/B_COPY/E/alpha' : Item(status='GG'),
    'A/B_COPY/E/'      : Item(status=' U'),
    })
  expected_status.tweak('A/B_COPY', status=' M', wc_rev=7)
  expected_status.tweak('A/B_COPY/E', status='  ', wc_rev=8)
  expected_status.tweak('A/B_COPY/E/alpha', wc_rev=8)
  expected_status.tweak('A/B_COPY/E/beta', wc_rev=8)
  expected_disk.tweak('A/B_COPY',
                      props={SVN_PROP_MERGEINFO : '/A/B:3-5,7'})
  expected_disk.tweak('A/B_COPY/E',
                      props={SVN_PROP_MERGEINFO : '/A/B/E:3-5,7'})
  expected_disk.tweak('A/B_COPY/E/alpha', contents="More new content",
                      props={})
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None,
                                        None, None, 1, E_COPY_path)


#----------------------------------------------------------------------
# If the update editor receives add_file(foo, copyfrom='blah'), it
# should attempt to locate 'blah' in the wc, and then copy it into place.

def update_handles_copyfrom(sbox):
  "update should make use of copyfrom args"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Make a backup copy of the working copy.
  wc_backup = sbox.add_wc_path('backup')
  svntest.actions.duplicate_dir(wc_dir, wc_backup)

  # Copy 'rho' to 'glub'
  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
  glub_path = os.path.join(wc_dir, 'A', 'D', 'G', 'glub')
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'copy', rho_path, glub_path)

  # Commit that change, creating r2.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/G/glub' : Item(verb='Adding'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/D/G/glub' : Item(status='  ', wc_rev=2),
    })
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Make a local edits to rho in the backup working copy - both text and props
  rho2_path = os.path.join(wc_backup, 'A', 'D', 'G', 'rho')
  svntest.main.file_append(rho2_path, "Some new text.\n")
  svntest.main.run_svn(None, 'propset', 'Kubla', 'Khan', rho2_path)

  # Now try updating our backup working copy: it should receive glub,
  # but with copyfrom args of rho@1, and thus copy the existing
  # (edited) rho to glub.  In other words, both rho and glub should be
  # identical and contain the same local edits.

  expected_output = svntest.wc.State(wc_backup, { })
  expected_output = wc.State(wc_backup, {
    'A/D/G/glub' : Item(status='A '),  ### perhaps update should show 'A +' ??
    })

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/D/G/rho',
                      contents="This is the file 'rho'.\nSome new text.\n",
                      props={'Kubla' : 'Khan'})
  expected_disk.add({
    'A/D/G/glub' : Item("This is the file 'rho'.\nSome new text.\n",
                        props={'Kubla' : 'Khan'})
    })

  expected_status = svntest.actions.get_virginal_state(wc_backup, 2)
  expected_status.tweak('A/D/G/rho', wc_rev=2, status='MM')
  expected_status.add({
    'A/D/G/glub' : Item(status='MM', wc_rev=2),
    })
  svntest.actions.run_and_verify_update(wc_backup,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        check_props = True)

#----------------------------------------------------------------------
# if the update_editor receives add_file(copyfrom=...), and the
# copyfrom_path simply isn't available in the working copy, it should
# fall back to doing an RA request to fetch the file.

def copyfrom_degrades_gracefully(sbox):
  "update degrades well if copyfrom_path unavailable"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Make a backup copy of the working copy.
  wc_backup = sbox.add_wc_path('backup')
  svntest.actions.duplicate_dir(wc_dir, wc_backup)

  # Move 'alpha' to 'glub'
  alpha_path = os.path.join(wc_dir, 'A', 'B', 'E', 'alpha')
  glub_path = os.path.join(wc_dir, 'A', 'D', 'G', 'glub')
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'mv', alpha_path, glub_path)

  # Commit that change, creating r2.
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/E/alpha' : Item(verb='Deleting'),
    'A/D/G/glub' : Item(verb='Adding'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/D/G/glub' : Item(status='  ', wc_rev=2),
    })
  expected_status.remove('A/B/E/alpha')
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # In the 2nd working copy, update just one side of the move -- so that
  # alpha gets deleted, but glub not yet added.
  E_path = os.path.join(wc_backup, 'A', 'B', 'E')
  expected_output = svntest.wc.State(E_path, {
      'alpha' : Item(status='D '),
      })
  expected_disk = wc.State('', {
      'beta'  : wc.StateItem("This is the file 'beta'.\n"),
      })
  expected_status = svntest.wc.State(E_path, {
    ''           : Item(status='  '),
    'beta'     : Item(status='  '),
    })
  expected_status.tweak(wc_rev=2)
  svntest.actions.run_and_verify_update(E_path,
                                        expected_output,
                                        expected_disk,
                                        expected_status)

  # Now update the entire working copy, which should cause an
  # add_file(glub, copyfrom_path=alpha)... except alpha is already gone.
  # Update editor should gracefully fetch it via RA request.
  expected_output = svntest.wc.State(wc_backup, { })
  expected_output = wc.State(wc_backup, {
    'A/D/G/glub' : Item(status='A '),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/B/E/alpha')
  expected_disk.add({
    'A/D/G/glub' : Item("This is the file 'alpha'.\n"),
    })
  expected_status = svntest.actions.get_virginal_state(wc_backup, 2)
  expected_status.remove('A/B/E/alpha')
  expected_status.add({
    'A/D/G/glub' : Item(status='  ', wc_rev=2),
    })
  svntest.actions.run_and_verify_update(wc_backup,
                                        expected_output,
                                        expected_disk,
                                        expected_status)

#----------------------------------------------------------------------
# If the update editor receives add_file(foo, copyfrom='blah'), it
# should attempt to locate 'blah' in the wc, and then copy it into
# place.  Furthermore, the new file should be able to receive
# subsequent txdeltas coming from the server.

def update_handles_copyfrom_with_txdeltas(sbox):
  "update uses copyfrom & accepts further txdeltas"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Make a backup copy of the working copy.
  wc_backup = sbox.add_wc_path('backup')
  svntest.actions.duplicate_dir(wc_dir, wc_backup)

  # Copy 'rho' to 'glub'
  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
  glub_path = os.path.join(wc_dir, 'A', 'D', 'G', 'glub')
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'copy', rho_path, glub_path)

  # Commit that change, creating r2.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/G/glub' : Item(verb='Adding'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/D/G/glub' : Item(status='  ', wc_rev=2),
    })
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Make additional edits to glub...
  svntest.main.file_append_binary(glub_path, "Some new text.\n")
  svntest.main.run_svn(None, 'propset', 'Kubla', 'Khan', glub_path)

  # Commit the changes, creating r3.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/G/glub' : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/D/G/glub' : Item(status='  ', wc_rev=3),
    })
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Make a local edit to rho in the backup working copy.
  rho2_path = os.path.join(wc_backup, 'A', 'D', 'G', 'rho')
  svntest.main.file_write(rho2_path,
                          "New first line.\nThis is the file 'rho'.\n",
                          "wb")

  # Now try updating our backup working copy: it should receive glub,
  # but with copyfrom args of rho@1, and thus copy the existing rho to
  # glub.  Furthermore, it should then apply the extra r3 edits to the
  # copied file.

  expected_output = svntest.wc.State(wc_backup, { })
  expected_output = wc.State(wc_backup, {
    'A/D/G/glub' : Item(status='A '),  ### perhaps update should show 'A +' ??
    })

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/D/G/rho',
                      contents="New first line.\nThis is the file 'rho'.\n")
  expected_disk.add({
    'A/D/G/glub' : Item("New first line.\nThis is the file 'rho'.\nSome new text.\n",
                        props={'Kubla' : 'Khan'})
    })

  expected_status = svntest.actions.get_virginal_state(wc_backup, 3)
  expected_status.tweak('A/D/G/rho', wc_rev=3, status='M ')
  expected_status.add({
    'A/D/G/glub' : Item(status='M ', wc_rev=3),
    })
  svntest.actions.run_and_verify_update(wc_backup,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        check_props = True)

#----------------------------------------------------------------------
# Very obscure bug: Issue #2977.
# Let's say there's a revision with
#   $ svn mv b c
#   $ svn mv a b
#   $ svn ci
# and a later revision that modifies b.  We then try a fresh checkout.  If
# the server happens to send us 'b' first, then when it later gets 'c'
# (with a copyfrom of 'b') it might try to use the 'b' in the wc as the
# copyfrom base.  This is wrong, because 'b' was changed later; however,
# due to a bug, the setting of svn:entry:committed-rev on 'b' is not being
# properly seen by the client, and it chooses the wrong base.  Corruption!
#
# Note that because this test depends on the order that the server sends
# changes, it is very fragile; even changing the file names can avoid
# triggering the bug.

def update_copied_from_replaced_and_changed(sbox):
  "update chooses right copyfrom for double move"

  sbox.build()
  wc_dir = sbox.wc_dir

  fn1_relpath = os.path.join('A', 'B', 'E', 'aardvark')
  fn2_relpath = os.path.join('A', 'B', 'E', 'alpha')
  fn3_relpath = os.path.join('A', 'B', 'E', 'beta')
  fn1_path = os.path.join(wc_dir, fn1_relpath)
  fn2_path = os.path.join(wc_dir, fn2_relpath)
  fn3_path = os.path.join(wc_dir, fn3_relpath)

  # Move fn2 to fn1
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'mv', fn2_path, fn1_path)

  # Move fn3 to fn2
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'mv', fn3_path, fn2_path)

  # Commit that change, creating r2.
  expected_output = svntest.wc.State(wc_dir, {
    fn1_relpath : Item(verb='Adding'),
    fn2_relpath : Item(verb='Replacing'),
    fn3_relpath : Item(verb='Deleting'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove(fn2_relpath, fn3_relpath)
  expected_status.add({
    fn1_relpath : Item(status='  ', wc_rev=2),
    fn2_relpath : Item(status='  ', wc_rev=2),
    })
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Modify fn2.
  fn2_final_contents = "I have new contents for the middle file."
  svntest.main.file_write(fn2_path, fn2_final_contents)

  # Commit the changes, creating r3.
  expected_output = svntest.wc.State(wc_dir, {
    fn2_relpath : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove(fn2_relpath, fn3_relpath)
  expected_status.add({
    fn1_relpath : Item(status='  ', wc_rev=2),
    fn2_relpath : Item(status='  ', wc_rev=3),
    })
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Go back to r1.
  expected_output = svntest.wc.State(wc_dir, {
    fn1_relpath: Item(status='D '),
    fn2_relpath: Item(status='A '), # though actually should be D and A
    fn3_relpath: Item(status='A '),
    })
  # Create expected disk tree for the update to rev 0
  expected_disk = svntest.main.greek_state.copy()
  # Do the update and check the results.
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        None, None,
                                        None, None, None, None, 0,
                                        '-r', '1', wc_dir)

  # And back up to 3 again.
  expected_output = svntest.wc.State(wc_dir, {
    fn1_relpath: Item(status='A '),
    fn2_relpath: Item(status='A '), # though actually should be D and A
    fn3_relpath: Item(status='D '),
    })
  # Create expected disk tree for the update to rev 0
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    fn1_relpath : Item("This is the file 'alpha'.\n"),
    })
  expected_disk.tweak(fn2_relpath, contents=fn2_final_contents)
  expected_disk.remove(fn3_relpath)
  # reuse old expected_status, but at r3
  expected_status.tweak(wc_rev=3)
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status, None,
                                        None, None, None, None, 0,
                                        wc_dir)

#----------------------------------------------------------------------
# Regression test: ra_neon assumes that you never delete a property on
# a newly-added file, which is wrong if it's add-with-history.
def update_copied_and_deleted_prop(sbox):
  "updating a copied file with a deleted property"

  sbox.build()
  wc_dir = sbox.wc_dir
  iota_path = os.path.join(wc_dir, 'iota')
  iota2_path = os.path.join(wc_dir, 'iota2')

  # Add a property on iota
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'propset', 'foo', 'bar', iota_path)
  # Commit that change, creating r2.
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(verb='Sending'),
    })
  expected_status_mixed = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status_mixed.tweak('iota', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status_mixed, None, wc_dir)

  # Copy iota to iota2 and delete the property on it.
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'copy', iota_path, iota2_path)
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'propdel', 'foo', iota2_path)

  # Commit that change, creating r3.
  expected_output = svntest.wc.State(wc_dir, {
    'iota2' : Item(verb='Adding'),
    })
  expected_status_mixed.add({
    'iota2' : Item(status='  ', wc_rev=3),
    })
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status_mixed, None, wc_dir)

  # Update the whole wc, verifying disk as well.
  expected_output = svntest.wc.State(wc_dir, { })
  expected_disk_r3 = svntest.main.greek_state.copy()
  expected_disk_r3.add({
    'iota2' : Item("This is the file 'iota'.\n"),
    })
  expected_disk_r3.tweak('iota', props={'foo':'bar'})
  expected_status_r3 = expected_status_mixed.copy()
  expected_status_r3.tweak(wc_rev=3)
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk_r3,
                                        expected_status_r3,
                                        check_props=True)

  # Now go back to r2.
  expected_output = svntest.wc.State(wc_dir, {'iota2': Item(status='D ')})
  expected_disk_r2 = expected_disk_r3.copy()
  expected_disk_r2.remove('iota2')
  expected_status_r2 = expected_status_r3.copy()
  expected_status_r2.tweak(wc_rev=2)
  expected_status_r2.remove('iota2')
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk_r2,
                                        expected_status_r2,
                                        None, None, None, None, None,
                                        True,
                                        "-r2", wc_dir)

  # And finally, back to r3, getting an add-with-history-and-property-deleted
  expected_output = svntest.wc.State(wc_dir, {'iota2': Item(status='A ')})
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk_r3,
                                        expected_status_r3,
                                        check_props=True)

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


def update_accept_conflicts(sbox):
  "update --accept automatic conflict resolution"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Make a backup copy of the working copy
  wc_backup = sbox.add_wc_path('backup')
  svntest.actions.duplicate_dir(wc_dir, wc_backup)

  # Make a few local mods to files which will be committed
  iota_path = os.path.join(wc_dir, 'iota')
  lambda_path = os.path.join(wc_dir, 'A', 'B', 'lambda')
  mu_path = os.path.join(wc_dir, 'A', 'mu')
  alpha_path = os.path.join(wc_dir, 'A', 'B', 'E', 'alpha')
  beta_path = os.path.join(wc_dir, 'A', 'B', 'E', 'beta')
  pi_path = os.path.join(wc_dir, 'A', 'D', 'G', 'pi')
  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
  svntest.main.file_append(lambda_path, 'Their appended text for lambda\n')
  svntest.main.file_append(iota_path, 'Their appended text for iota\n')
  svntest.main.file_append(mu_path, 'Their appended text for mu\n')
  svntest.main.file_append(alpha_path, 'Their appended text for alpha\n')
  svntest.main.file_append(beta_path, 'Their appended text for beta\n')
  svntest.main.file_append(pi_path, 'Their appended text for pi\n')
  svntest.main.file_append(rho_path, 'Their appended text for rho\n')

  # Make a few local mods to files which will be conflicted
  iota_path_backup = os.path.join(wc_backup, 'iota')
  lambda_path_backup = os.path.join(wc_backup, 'A', 'B', 'lambda')
  mu_path_backup = os.path.join(wc_backup, 'A', 'mu')
  alpha_path_backup = os.path.join(wc_backup, 'A', 'B', 'E', 'alpha')
  beta_path_backup = os.path.join(wc_backup, 'A', 'B', 'E', 'beta')
  pi_path_backup = os.path.join(wc_backup, 'A', 'D', 'G', 'pi')
  rho_path_backup = os.path.join(wc_backup, 'A', 'D', 'G', 'rho')
  svntest.main.file_append(iota_path_backup,
                           'My appended text for iota\n')
  svntest.main.file_append(lambda_path_backup,
                           'My appended text for lambda\n')
  svntest.main.file_append(mu_path_backup,
                           'My appended text for mu\n')
  svntest.main.file_append(alpha_path_backup,
                           'My appended text for alpha\n')
  svntest.main.file_append(beta_path_backup,
                           'My appended text for beta\n')
  svntest.main.file_append(pi_path_backup,
                           'My appended text for pi\n')
  svntest.main.file_append(rho_path_backup,
                           'My appended text for rho\n')

  # Created expected output tree for 'svn ci'
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(verb='Sending'),
    'A/B/lambda' : Item(verb='Sending'),
    'A/mu' : Item(verb='Sending'),
    'A/B/E/alpha': Item(verb='Sending'),
    'A/B/E/beta': Item(verb='Sending'),
    'A/D/G/pi' : Item(verb='Sending'),
    'A/D/G/rho' : Item(verb='Sending'),
    })

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', wc_rev=2)
  expected_status.tweak('A/B/lambda', wc_rev=2)
  expected_status.tweak('A/mu', wc_rev=2)
  expected_status.tweak('A/B/E/alpha', wc_rev=2)
  expected_status.tweak('A/B/E/beta', wc_rev=2)
  expected_status.tweak('A/D/G/pi', wc_rev=2)
  expected_status.tweak('A/D/G/rho', wc_rev=2)

  # Commit.
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Now we'll update each of our 5 files in wc_backup; each one will get
  # conflicts, and we'll handle each with a different --accept option.

  # Setup SVN_EDITOR and SVN_MERGE for --accept={edit,launch}.
  svntest.main.use_editor('append_foo')

  # iota: no accept option
  # Just leave the conflicts alone, since run_and_verify_svn already uses
  # the --non-interactive option.
  svntest.actions.run_and_verify_svn(None,
                                     ['C    %s\n' % (iota_path_backup,),
                                      'Updated to revision 2.\n',
                                      'Summary of conflicts:\n',
                                      '  Text conflicts: 1\n'],
                                     [],
                                     'update', iota_path_backup)

  # lambda: --accept=postpone
  # Just leave the conflicts alone.
  svntest.actions.run_and_verify_svn(None,
                                     ['C    %s\n' % (lambda_path_backup,),
                                      'Updated to revision 2.\n',
                                      'Summary of conflicts:\n',
                                      '  Text conflicts: 1\n'],
                                     [],
                                     'update', '--accept=postpone',
                                     lambda_path_backup)

  # mu: --accept=base
  # Accept the pre-update base file.
  svntest.actions.run_and_verify_svn(None,
                                     ['G    %s\n' % (mu_path_backup,),
                                      'Updated to revision 2.\n'],
                                     [],
                                     'update', '--accept=base',
                                     mu_path_backup)

  # alpha: --accept=mine
  # Accept the user's working file.
  svntest.actions.run_and_verify_svn(None,
                                     ['G    %s\n' % (alpha_path_backup,),
                                      'Updated to revision 2.\n'],
                                     [],
                                     'update', '--accept=mine-full',
                                     alpha_path_backup)

  # beta: --accept=theirs
  # Accept their file.
  svntest.actions.run_and_verify_svn(None,
                                     ['G    %s\n' % (beta_path_backup,),
                                      'Updated to revision 2.\n'],
                                     [],
                                     'update', '--accept=theirs-full',
                                     beta_path_backup)

  # pi: --accept=edit
  # Run editor and accept the edited file. The merge tool will leave
  # conflicts in place, so expect a message on stderr, but expect
  # svn to exit with an exit code of 0.
  svntest.actions.run_and_verify_svn2(None,
                                      ['G    %s\n' % (pi_path_backup,),
                                       'Updated to revision 2.\n'],
                                      "system(.*) returned.*", 0,
                                      'update', '--accept=edit',
                                      pi_path_backup)

  # rho: --accept=launch
  # Run the external merge tool, it should leave conflict markers in place.
  svntest.actions.run_and_verify_svn(None,
                                     ['C    %s\n' % (rho_path_backup,),
                                      'Updated to revision 2.\n',
                                      'Summary of conflicts:\n',
                                      '  Text conflicts: 1\n'],
                                     [],
                                     'update', '--accept=launch',
                                     rho_path_backup)

  # Set the expected disk contents for the test
  expected_disk = svntest.main.greek_state.copy()

  expected_disk.tweak('iota', contents=("This is the file 'iota'.\n"
                                        '<<<<<<< .mine\n'
                                        'My appended text for iota\n'
                                        '=======\n'
                                        'Their appended text for iota\n'
                                        '>>>>>>> .r2\n'))
  expected_disk.tweak('A/B/lambda', contents=("This is the file 'lambda'.\n"
                                              '<<<<<<< .mine\n'
                                              'My appended text for lambda\n'
                                              '=======\n'
                                              'Their appended text for lambda\n'
                                              '>>>>>>> .r2\n'))
  expected_disk.tweak('A/mu', contents="This is the file 'mu'.\n")
  expected_disk.tweak('A/B/E/alpha', contents=("This is the file 'alpha'.\n"
                                               'My appended text for alpha\n'))
  expected_disk.tweak('A/B/E/beta', contents=("This is the file 'beta'.\n"
                                              'Their appended text for beta\n'))
  expected_disk.tweak('A/D/G/pi', contents=("This is the file 'pi'.\n"
                                             '<<<<<<< .mine\n'
                                             'My appended text for pi\n'
                                             '=======\n'
                                             'Their appended text for pi\n'
                                             '>>>>>>> .r2\n'
                                             'foo\n'))
  expected_disk.tweak('A/D/G/rho', contents=("This is the file 'rho'.\n"
                                             '<<<<<<< .mine\n'
                                             'My appended text for rho\n'
                                             '=======\n'
                                             'Their appended text for rho\n'
                                             '>>>>>>> .r2\n'
                                             'foo\n'))

  # Set the expected extra files for the test
  extra_files = ['iota.*\.r1', 'iota.*\.r2', 'iota.*\.mine',
                 'lambda.*\.r1', 'lambda.*\.r2', 'lambda.*\.mine',
                 'rho.*\.r1', 'rho.*\.r2', 'rho.*\.mine']

  # Set the expected status for the test
  expected_status = svntest.actions.get_virginal_state(wc_backup, 2)
  expected_status.tweak('iota', 'A/B/lambda', 'A/mu',
                        'A/B/E/alpha', 'A/B/E/beta',
                        'A/D/G/pi', 'A/D/G/rho', wc_rev=2)
  expected_status.tweak('iota', status='C ')
  expected_status.tweak('A/B/lambda', status='C ')
  expected_status.tweak('A/mu', status='M ')
  expected_status.tweak('A/B/E/alpha', status='M ')
  expected_status.tweak('A/B/E/beta', status='  ')
  expected_status.tweak('A/D/G/pi', status='M ')
  expected_status.tweak('A/D/G/rho', status='C ')

  # Set the expected output for the test
  expected_output = wc.State(wc_backup, {})

  # Do the update and check the results in three ways.
  svntest.actions.run_and_verify_update(wc_backup,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None,
                                        svntest.tree.detect_conflict_files,
                                        extra_files)

# Test for a wc corruption race condition (possibly introduced in
# r23342) which is easy to trigger if interactive conflict resolution
# dies in the middle of prompting.  Specifically, we run an update
# with interactive-conflicts on but close stdin immediately, so the
# prompt errors out; then the dir_baton pool cleanup handlers in the
# WC update editor flush and run incomplete logs and lead to WC
# corruption, detectable by another update command.

def eof_in_interactive_conflict_resolver(sbox):
  "eof in interactive resolution can't break wc"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Set up a custom config directory which *doesn't* turn off
  # interactive resolution
  config_contents = '''\
[miscellany]
interactive-conflicts = true
'''
  tmp_dir = os.path.abspath(svntest.main.temp_dir)
  config_dir = os.path.join(tmp_dir, 'interactive-conflicts-config')
  svntest.main.create_config_dir(config_dir, config_contents)

  iota_path = os.path.join(wc_dir, 'iota')

  # Modify iota and commit for r2.
  svntest.main.file_append(iota_path, "Appended text in r2.\n")
  expected_output = svntest.wc.State(wc_dir, {
    'iota': Item(verb="Sending"),
  })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Go back to revision 1.
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(status='U '),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None,
                                        None, None,
                                        None, None, 1,
                                        '-r1', wc_dir)

  # Modify iota differently and try to update *with the interactive
  # resolver*.  ### The parser won't go so well with the output
  svntest.main.file_append(iota_path, "Local mods to r1 text.\n")
  svntest.actions.run_and_verify_update(wc_dir, None, None, None,
                                        "Can't read stdin: End of file found",
                                        None, None, None, None, 1,
                                        wc_dir, '--config-dir', config_dir)

  # Now update -r1 again.  Hopefully we don't get a checksum error!
  expected_output = svntest.wc.State(wc_dir, {})
  # note: it's possible that the correct disk here should be the
  # merged file?
  expected_disk.tweak('iota', contents=("This is the file 'iota'.\n"
                                        "Local mods to r1 text.\n"))
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', status='M ')
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None,
                                        None, None,
                                        None, None, 1,
                                        '-r1', wc_dir)


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


def update_uuid_changed(sbox):
  "update fails when repos uuid changed"

  # read_only=False, since we don't want to run setuuid on the (shared)
  # pristine repository.
  sbox.build(read_only = False)

  wc_dir = sbox.wc_dir
  repo_dir = sbox.repo_dir

  uuid_before = svntest.actions.get_wc_uuid(wc_dir)

  # Change repository's uuid.
  svntest.actions.run_and_verify_svnadmin(None, None, [],
                                          'setuuid', repo_dir)

  # 'update' detected the new uuid...
  svntest.actions.run_and_verify_svn(None, None, '.*UUID.*',
                                     'update', wc_dir)

  # ...and didn't overwrite the old uuid.
  uuid_after = svntest.actions.get_wc_uuid(wc_dir)
  if uuid_before != uuid_after:
    raise svntest.Failure


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

# Issue #1672: if an update deleting a dir prop is interrupted (by a
# local obstruction, for example) then restarting the update will not
# delete the prop, causing the wc to become out of sync with the
# repository.
def restarted_update_should_delete_dir_prop(sbox):
  "restarted update should delete dir prop"
  sbox.build()
  wc_dir = sbox.wc_dir

  A_path = os.path.join(wc_dir, 'A')
  zeta_path = os.path.join(A_path, 'zeta')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)

  # Commit a propset on A.
  svntest.main.run_svn(None, 'propset', 'prop', 'val', A_path)
  expected_output = svntest.wc.State(wc_dir, {
    'A': Item(verb='Sending'),
    })
  expected_status.tweak('A', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # Create a second working copy.
  other_wc = sbox.add_wc_path('other')
  svntest.actions.duplicate_dir(wc_dir, other_wc)

  other_A_path = os.path.join(other_wc, 'A')
  other_zeta_path = os.path.join(other_wc, 'A', 'zeta')

  # In the second working copy, delete A's prop and add a new file.
  svntest.main.run_svn(None, 'propdel', 'prop', other_A_path)
  svntest.main.file_write(other_zeta_path, 'New file\n')
  svntest.main.run_svn(None, 'add', other_zeta_path)
  expected_output = svntest.wc.State(other_wc, {
    'A': Item(verb='Sending'),
    'A/zeta' : Item(verb='Adding'),
    })
  expected_status = svntest.actions.get_virginal_state(other_wc, 1)
  expected_status.tweak('A', wc_rev=3)
  expected_status.add({
    'A/zeta' : Item(status='  ', wc_rev=3),
    })
  svntest.actions.run_and_verify_commit(other_wc, expected_output,
                                        expected_status, None, other_wc)

  # Back in the first working copy, create an obstructing path and
  # update. The update will be interrupted, resulting in an incomplete
  # dir which still has the property.
  svntest.main.file_write(zeta_path, 'Obstructing file\n')
  error_re = 'Failed to add file.*file of the same name already exists'
  svntest.actions.run_and_verify_update(wc_dir, None, None, None,
                                        error_re)

  # Now, delete the obstructing path and rerun the update.
  # A's property should disappear.
  os.unlink(zeta_path)

  expected_output = svntest.wc.State(wc_dir, {
    'A'      : Item(status=' U'),
    'A/zeta' : Item(status='A '),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A', props = {})
  expected_disk.add({
    'A/zeta' : Item("New file\n"),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 3)
  expected_status.add({
    'A/zeta' : Item(status='  ', wc_rev=3),
    })

  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        check_props = True)

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

# Detect tree conflicts among files and directories,
# edited or deleted in a deep directory structure.
#
# See use cases 1-3 in notes/tree-conflicts/use-cases.txt for background.

# convenience definitions
leaf_edit = svntest.actions.deep_trees_leaf_edit
tree_del = svntest.actions.deep_trees_tree_del
leaf_del = svntest.actions.deep_trees_leaf_del

disk_after_leaf_edit = svntest.actions.deep_trees_after_leaf_edit
disk_after_leaf_del = svntest.actions.deep_trees_after_leaf_del
disk_after_tree_del = svntest.actions.deep_trees_after_tree_del

disk_empty_dirs = svntest.actions.deep_trees_empty_dirs

deep_trees_conflict_output = svntest.actions.deep_trees_conflict_output
deep_trees_conflict_output_skipped = \
    svntest.actions.deep_trees_conflict_output_skipped
deep_trees_status_local_tree_del = \
    svntest.actions.deep_trees_status_local_tree_del
deep_trees_status_local_leaf_edit = \
    svntest.actions.deep_trees_status_local_leaf_edit

DeepTreesTestCase = svntest.actions.DeepTreesTestCase


def tree_conflicts_on_update_1_1(sbox):
  "tree conflicts 1.1: tree del, leaf edit on update"

  # use case 1, as in notes/tree-conflicts/use-cases.txt
  # 1.1) local tree delete, incoming leaf edit

  expected_output = deep_trees_conflict_output

  expected_disk = disk_empty_dirs.copy()

  # The files delta, epsilon, and zeta are incoming additions, but since
  # they are all within locally deleted trees they should also be schedule
  # for deletion.
  expected_status = deep_trees_status_local_tree_del.copy()
  expected_status.add({
    'D/D1/delta'        : Item(status='D '),
    'DD/D1/D2/epsilon'  : Item(status='D '),
    'DDD/D1/D2/D3/zeta' : Item(status='D '),
    })

  # Update to the target rev.
  expected_status.tweak(wc_rev=3)

  svntest.actions.deep_trees_run_tests_scheme_for_update(sbox,
    [ DeepTreesTestCase("local_tree_del_incoming_leaf_edit",
                        tree_del,
                        leaf_edit,
                        expected_output,
                        expected_disk,
                        expected_status) ] )


def tree_conflicts_on_update_1_2(sbox):
  "tree conflicts 1.2: tree del, leaf del on update"

  # 1.2) local tree delete, incoming leaf delete

  expected_output = deep_trees_conflict_output

  expected_disk = disk_empty_dirs.copy()

  expected_status = deep_trees_status_local_tree_del.copy()

  # Expect the incoming leaf deletes to actually occur.  Even though they
  # are within (or in the case of F/alpha and D/D1 are the same as) the
  # trees locally scheduled for deletion we must still delete them and
  # update the scheduled for deletion items to the target rev.  Otherwise
  # once the conflicts are resolved we still have a mixed-rev WC we can't
  # commit without updating...which, you guessed it, raises tree conflicts
  # again, repeat ad infinitum - see issue #3334.
  #
  # Update to the target rev.
  expected_status.tweak(wc_rev=3)
  expected_status.tweak('F/alpha',
                        'D/D1',
                        status='! ', wc_rev=None)
  # Remove the incoming deletes from status and disk.
  expected_status.remove('DD/D1/D2',
                         'DDD/D1/D2/D3',
                         'DDF/D1/D2/gamma',
                         'DF/D1/beta')
  ### Why does the deep trees state not include files?
  expected_disk.remove('D/D1',
                       'DD/D1/D2',
                       'DDD/D1/D2/D3')

  svntest.actions.deep_trees_run_tests_scheme_for_update(sbox,
    [ DeepTreesTestCase("local_tree_del_incoming_leaf_del",
                        tree_del,
                        leaf_del,
                        expected_output,
                        expected_disk,
                        expected_status) ] )


def tree_conflicts_on_update_2_1(sbox):
  "tree conflicts 2.1: leaf edit, tree del on update"

  # use case 2, as in notes/tree-conflicts/use-cases.txt
  # 2.1) local leaf edit, incoming tree delete

  expected_output = deep_trees_conflict_output

  expected_disk = disk_after_leaf_edit

  expected_status = deep_trees_status_local_leaf_edit.copy()
  # Adjust the status of the roots of the six subtrees scheduled for deletion
  # during the update.  Since these are all tree conflicts, they will all be
  # scheduled for addition as copies with history - see Issue #3334.
  expected_status.tweak(
    'D/D1',
    'F/alpha',
    'DD/D1',
    'DF/D1',
    'DDD/D1',
    'DDF/D1',
    status='A ', copied='+', wc_rev='-')
  # See the status of all the paths *under* the above six subtrees.  Only the
  # roots of the added subtrees show as schedule 'A', these childs paths show
  # only that history is scheduled with the commit.
  expected_status.tweak(
    'DD/D1/D2',
    'DDD/D1/D2',
    'DDD/D1/D2/D3',
    'DF/D1/beta',
    'DDF/D1/D2',
    'DDF/D1/D2/gamma',
    copied='+', wc_rev='-')
  svntest.actions.deep_trees_run_tests_scheme_for_update(sbox,
    [ DeepTreesTestCase("local_leaf_edit_incoming_tree_del",
                        leaf_edit,
                        tree_del,
                        expected_output,
                        expected_disk,
                        expected_status) ] )



def tree_conflicts_on_update_2_2(sbox):
  "tree conflicts 2.2: leaf del, tree del on update"

  # 2.2) local leaf delete, incoming tree delete

  ### Current behaviour fails to show conflicts when deleting
  ### a directory tree that has modifications. (Will be solved
  ### when dirs_same_p() is implemented)
  expected_output = deep_trees_conflict_output

  expected_disk = disk_empty_dirs.copy()

  expected_status = svntest.actions.deep_trees_virginal_state.copy()
  expected_status.add({'' : Item()})
  expected_status.tweak(contents=None, status='  ', wc_rev=3)
  # Tree conflicts.
  expected_status.tweak(
    'D/D1',
    'F/alpha',
    'DD/D1',
    'DF/D1',
    'DDD/D1',
    'DDF/D1',
    treeconflict='C', wc_rev=2)

  # Expect the incoming tree deletes and the local leaf deletes to mean
  # that all deleted paths are *really* gone, not simply scheduled for
  # deletion.
  expected_status.tweak('F/alpha',
                        'D/D1',
                        'DD/D1',
                        'DF/D1',
                        'DDD/D1',
                        'DDF/D1',
                        status='! ', wc_rev=None)
  # Remove from expected status and disk everything below the deleted paths.
  expected_status.remove('DD/D1/D2',
                         'DF/D1/beta',
                         'DDD/D1/D2',
                         'DDD/D1/D2/D3',
                         'DDF/D1/D2',
                         'DDF/D1/D2/gamma',)
  expected_disk.remove('D/D1',
                       'DD/D1',
                       'DD/D1/D2',
                       'DF/D1',
                       'DDD/D1',
                       'DDD/D1/D2',
                       'DDD/D1/D2/D3',
                       'DDF/D1',
                       'DDF/D1/D2',)
  svntest.actions.deep_trees_run_tests_scheme_for_update(sbox,
    [ DeepTreesTestCase("local_leaf_del_incoming_tree_del",
                        leaf_del,
                        tree_del,
                        expected_output,
                        expected_disk,
                        expected_status) ] )


#----------------------------------------------------------------------
# Test for issue #3329 'Update throws error when skipping some tree
# conflicts'
#
# Marked as XFail until issue #3329 is resolved.
def tree_conflicts_on_update_2_3(sbox):
  "tree conflicts 2.3: skip on 2nd update"

  # Test that existing tree conflicts are skipped

  expected_output = deep_trees_conflict_output_skipped

  expected_disk = disk_after_leaf_edit

  expected_status = deep_trees_status_local_leaf_edit.copy()

  # Adjust the status of the roots of the six subtrees scheduled for deletion
  # during the update.  Since these are all tree conflicts, they will all be
  # scheduled for addition as copies with history - see Issue #3334.
  expected_status.tweak(
    'D/D1',
    'F/alpha',
    'DD/D1',
    'DF/D1',
    'DDD/D1',
    'DDF/D1',
    status='A ', copied='+', wc_rev='-')
  # See the status of all the paths *under* the above six subtrees.  Only the
  # roots of the added subtrees show as schedule 'A', these child paths show
  # only that history is scheduled with the commit.
  expected_status.tweak(
    'DD/D1/D2',
    'DDD/D1/D2',
    'DDD/D1/D2/D3',
    'DF/D1/beta',
    'DDF/D1/D2',
    'DDF/D1/D2/gamma',
    copied='+', wc_rev='-')

  # Paths where output should be a single 'Skipped' message.
  skip_paths = [
    'D/D1',
    'F/alpha',
    'DDD/D1',
    'DDD/D1/D2/D3',
    ]

  # This is where the test fails.  Repeat updates on '', 'D', 'F', or
  # 'DDD' report no skips.
  chdir_skip_paths = [
    ('D', 'D1'),
    ('F', 'alpha'),
    ('DDD', 'D1'),
    ('', 'D/D1', 'F/alpha', 'DD/D1', 'DF/D1', 'DDD/D1', 'DDF/D1'),
    ]
  # Note: We don't step *into* a directory that's deleted in the repository.
  # E.g. ('DDD/D1/D2', '') would correctly issue a "path does not
  # exist" error, because at that point it can't know about the
  # tree-conflict on DDD/D1. ('D/D1', '') likewise, as tree-conflict
  # information is stored in the parent of a victim directory.

  svntest.actions.deep_trees_skipping_on_update(sbox,
    DeepTreesTestCase("local_leaf_edit_incoming_tree_del",
                      leaf_edit,
                      tree_del,
                      expected_output,
                      expected_disk,
                      expected_status),
                                                skip_paths,
                                                chdir_skip_paths)


def tree_conflicts_on_update_3(sbox):
  "tree conflicts 3: tree del, tree del on update"

  # use case 3, as in notes/tree-conflicts/use-cases.txt
  # local tree delete, incoming tree delete

  expected_output = deep_trees_conflict_output

  expected_disk = disk_empty_dirs.copy()

  expected_status = deep_trees_status_local_tree_del.copy()

  # Expect the incoming tree deletes and the local tree deletes to mean
  # that all deleted paths are *really* gone, not simply scheduled for
  # deletion.
  expected_status.tweak('F/alpha',
                        'D/D1',
                        'DD/D1',
                        'DF/D1',
                        'DDD/D1',
                        'DDF/D1',
                        status='! ', wc_rev=None)
  # Remove from expected status and disk everything below the deleted paths.
  expected_status.remove('DD/D1/D2',
                         'DF/D1/beta',
                         'DDD/D1/D2',
                         'DDD/D1/D2/D3',
                         'DDF/D1/D2',
                         'DDF/D1/D2/gamma',)
  expected_disk.remove('D/D1',
                       'DD/D1',
                       'DD/D1/D2',
                       'DF/D1',
                       'DDD/D1',
                       'DDD/D1/D2',
                       'DDD/D1/D2/D3',
                       'DDF/D1',
                       'DDF/D1/D2',)

  svntest.actions.deep_trees_run_tests_scheme_for_update(sbox,
    [ DeepTreesTestCase("local_tree_del_incoming_tree_del",
                        tree_del,
                        tree_del,
                        expected_output,
                        expected_disk,
                        expected_status) ] )

#----------------------------------------------------------------------
# Test for issue #3354 'update fails when file with local mods is moved
# and modified'
#
# Marked as XFail until issue #3354 is resolved.
def update_moves_and_modifies_an_edited_file(sbox):
  "update moves and modifies a file with edits"

  # r1: Create our standard greek test tree.
  sbox.build()
  wc_dir = sbox.wc_dir

  # Make a second working copy
  other_wc = sbox.add_wc_path('other')
  svntest.actions.duplicate_dir(wc_dir, other_wc)
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)

  # Some paths we'll care about
  E_path           = os.path.join(wc_dir, "A", "B", "E")
  alpha_path       = os.path.join(wc_dir, "A", "B", "E", "alpha")
  alpha_moved_path = os.path.join(wc_dir, "A", "B", "E", "alpha.moved")
  other_alpha_path = os.path.join(other_wc, "A", "B", "E", "alpha")
  other_E_path     = os.path.join(other_wc, "A", "B", "E")

  # r2: Move A/B/E/alpha to A/B/E/alpha.moved in the first WC.
  svntest.actions.run_and_verify_svn(None, None, [], 'move',
                                     alpha_path, alpha_moved_path)
  expected_output = wc.State(wc_dir, {
    'A/B/E/alpha'       : Item(verb='Deleting'),
    'A/B/E/alpha.moved' : Item(verb='Adding')
    })
  expected_status.add({'A/B/E/alpha.moved' : Item(status='  ', wc_rev=2)})
  expected_status.remove('A/B/E/alpha')
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None, wc_dir)

  # r3: Make a text mod to A/B/E/alpha.moved in the first WC.
  new_content_for_alpha = 'alpha, modified after move\n'
  svntest.main.file_write(alpha_moved_path, new_content_for_alpha)
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/E/alpha.moved' : Item(verb='Sending'),
    })
  expected_status.tweak('A/B/E/alpha.moved', wc_rev=3)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status, None,
                                        wc_dir)

  # Make a text mod to A/B/E/alpha in the second WC then
  # update the second WC.
  new_content_for_other_alpha = 'alpha, modified\n'
  svntest.main.file_write(other_alpha_path, new_content_for_other_alpha)

  expected_output = wc.State(other_E_path, {
    'alpha'       : Item(status='  ', treeconflict='C'),
    'alpha.moved' : Item(status='C '),
    })
  expected_status = wc.State(other_E_path, {
    ''            : Item(status='  ', wc_rev=3),
    'alpha'       : Item(status='A ', wc_rev='-', copied='+', treeconflict='C'),
    'alpha.moved' : Item(status='C ', wc_rev=3),
    'beta'        : Item(status='  ', wc_rev=3),
    })
  expected_disk = wc.State('', {
    'alpha'              : Item(new_content_for_other_alpha),
    'alpha.moved'        : Item("<<<<<<< .mine\n" +
                                new_content_for_other_alpha +
                                "=======\n" +
                                new_content_for_alpha +
                                ">>>>>>> .r3\n"),
    'alpha.moved.copied' : Item("This is the file 'alpha'.\n"),
    'alpha.moved.r3'     : Item(new_content_for_alpha),
    'alpha.moved.mine'   : Item(new_content_for_other_alpha),
    'beta'               : Item("This is the file 'beta'.\n"),
    })

  # This update should succeed and leave A/B/E/alpha as scheduled for
  # addition with the local edit made prior to the update (i.e. this is
  # a tree conflict with the incoming delete half of the move in r2).
  # A/B/E/alpha.moved should also be present and have a text conflict
  # as a result of the incoming text edit in r3.
  #
  # Prior to the fix for issue #3354 this update failed and left the
  # WC locked.
  expected_skip = wc.State(other_E_path, { })
  svntest.actions.run_and_verify_update(other_E_path,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None, None, None,
                                        True, other_E_path,
                                        '--accept', 'postpone')

# Issue #3334: a modify-on-deleted tree conflict should leave the node
# updated to the target revision but still scheduled for deletion.
def tree_conflict_uc1_update_deleted_tree(sbox):
  "tree conflicts on update UC1, update deleted tree"
  sbox.build()
  wc_dir = sbox.wc_dir

  from svntest.actions import run_and_verify_svn, run_and_verify_resolve
  from svntest.actions import run_and_verify_update, run_and_verify_commit
  from svntest.verify import AnyOutput

  """A directory tree 'D1' should end up exactly the same in these two
  scenarios:

  New scenario:
  [[[
    svn checkout -r1             # in which D1 has its original state
    svn delete D1
    svn update -r2               # update revs & bases to r2
    svn resolve --accept=mine    # keep the local, deleted version
  ]]]

  Existing scenario:
  [[[
    svn checkout -r2             # in which D1 is already modified
    svn delete D1
  ]]]
  """

  A = os.path.join(wc_dir, 'A')
  A_url = sbox.repo_url + '/A'

  def modify_dir(dir):
    """Make some set of local modifications to an existing tree:
    A prop change, add a child, delete a child, change a child."""
    run_and_verify_svn(None, AnyOutput, [],
                       'propset', 'p', 'v', dir)
    path = os.path.join(dir, 'new_file')
    svntest.main.file_write(path, "This is the file 'new_file'.\n")
    svntest.actions.run_and_verify_svn(None, None, [], 'add', path)

    path = os.path.join(dir, 'C', 'N')
    os.mkdir(path)
    path2 = os.path.join(dir, 'C', 'N', 'nu')
    svntest.main.file_write(path2, "This is the file 'nu'.\n")
    svntest.actions.run_and_verify_svn(None, None, [], 'add', path)

    path = os.path.join(dir, 'B', 'lambda')
    svntest.actions.run_and_verify_svn(None, None, [], 'delete', path)

    path = os.path.join(dir, 'B', 'E', 'alpha')
    svntest.main.file_append(path, "An extra line.\n")

  # Prep for both scenarios
  modify_dir(A)
  run_and_verify_svn(None, AnyOutput, [], 'ci', A, '-m', 'modify_dir')
  run_and_verify_svn(None, AnyOutput, [], 'up', wc_dir)

  # Existing scenario
  wc2 = sbox.add_wc_path('wc2')
  A2 = os.path.join(wc2, 'A')
  svntest.actions.duplicate_dir(sbox.wc_dir, wc2)
  run_and_verify_svn(None, AnyOutput, [], 'delete', A2)

  # New scenario (starts at the revision before the committed mods)
  run_and_verify_svn(None, AnyOutput, [], 'up', A, '-r1')
  run_and_verify_svn(None, AnyOutput, [], 'delete', A)

  expected_output = None
  expected_disk = None
  expected_status = None

  run_and_verify_update(A, expected_output, expected_disk, expected_status)
  run_and_verify_resolve([A], '--recursive', '--accept=working', A)

  resolved_status = svntest.wc.State('', {
      ''            : Item(status='  ', wc_rev=2),
      'A'           : Item(status='D ', wc_rev=2),
      'A/B'         : Item(status='D ', wc_rev=2),
      'A/B/E'       : Item(status='D ', wc_rev=2),
      'A/B/E/alpha' : Item(status='D ', wc_rev=2),
      'A/B/E/beta'  : Item(status='D ', wc_rev=2),
      'A/B/F'       : Item(status='D ', wc_rev=2),
      'A/mu'        : Item(status='D ', wc_rev=2),
      'A/C'         : Item(status='D ', wc_rev=2),
      'A/C/N'       : Item(status='D ', wc_rev=2),
      'A/C/N/nu'    : Item(status='D ', wc_rev=2),
      'A/D'         : Item(status='D ', wc_rev=2),
      'A/D/gamma'   : Item(status='D ', wc_rev=2),
      'A/D/G'       : Item(status='D ', wc_rev=2),
      'A/D/G/pi'    : Item(status='D ', wc_rev=2),
      'A/D/G/rho'   : Item(status='D ', wc_rev=2),
      'A/D/G/tau'   : Item(status='D ', wc_rev=2),
      'A/D/H'       : Item(status='D ', wc_rev=2),
      'A/D/H/chi'   : Item(status='D ', wc_rev=2),
      'A/D/H/omega' : Item(status='D ', wc_rev=2),
      'A/D/H/psi'   : Item(status='D ', wc_rev=2),
      'A/new_file'  : Item(status='D ', wc_rev=2),
      'iota'        : Item(status='  ', wc_rev=2),
      })

  # The status of the new and old scenarios should be identical.
  expected_status = resolved_status.copy()
  expected_status.wc_dir = wc2
  svntest.actions.run_and_verify_status(wc2, expected_status)

  expected_status = resolved_status.copy()
  expected_status.wc_dir = wc_dir

  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Just for kicks, try to commit.
  expected_output = svntest.wc.State(wc_dir, {
      'A'           : Item(verb='Deleting'),
      })
  expected_status = svntest.wc.State(wc_dir, {
      ''            : Item(status='  ', wc_rev=2),
      'iota'        : Item(status='  ', wc_rev=2),
      })
  run_and_verify_commit(wc_dir, expected_output, expected_status,
                        None, wc_dir, '-m', 'commit resolved tree')


# Issue #3334: a delete-onto-modified tree conflict should leave the node
# scheduled for re-addition.
def tree_conflict_uc2_schedule_re_add(sbox):
  "tree conflicts on update UC2, schedule re-add"
  sbox.build()
  saved_cwd = os.getcwd()
  os.chdir(sbox.wc_dir)

  from svntest.actions import run_and_verify_svn, run_and_verify_resolve
  from svntest.actions import run_and_verify_update
  from svntest.verify import AnyOutput

  """A directory tree 'D1' should end up exactly the same in these two
  scenarios:

  New scenario:
  [[[
    svn checkout -r1             # in which D1 exists
    modify_d1                    # make local mods in D1
    svn update -r2               # tries to delete D1
    svn resolve --accept=mine    # keep the local, re-added version
  ]]]

  Existing scenario:
  [[[
    svn checkout -r2             # in which D1 does not exist
    svn copy -r1 D1 .            # make a pristine copy of D1@1
    modify_d1                    # make local mods in D1
  ]]]

  where modify_d1 makes property changes to D1 itself and/or
  adds/deletes/modifies any of D1's children.
  """

  dir = 'A'  # an existing tree in the WC and repos
  dir_url = sbox.repo_url + '/' + dir

  def modify_dir(dir):
    """Make some set of local modifications to an existing tree:
    A prop change, add a child, delete a child, change a child."""
    run_and_verify_svn(None, AnyOutput, [],
                       'propset', 'p', 'v', dir)
    path = os.path.join(dir, 'new_file')
    svntest.main.file_write(path, "This is the file 'new_file'.\n")
    svntest.actions.run_and_verify_svn(None, None, [], 'add', path)

    path = os.path.join(dir, 'B', 'lambda')
    svntest.actions.run_and_verify_svn(None, None, [], 'delete', path)

    path = os.path.join(dir, 'B', 'E', 'alpha')
    svntest.main.file_append(path, "An extra line.\n")

  # Prepare the repos so that a later 'update' has an incoming deletion:
  # Delete the dir in the repos, making r2
  run_and_verify_svn(None, AnyOutput, [],
                     '-m', '', 'delete', dir_url)

  # Existing scenario
  os.chdir(saved_cwd)
  wc2 = sbox.add_wc_path('wc2')
  dir2 = os.path.join(wc2, dir)
  svntest.actions.duplicate_dir(sbox.wc_dir, wc2)
  run_and_verify_svn(None, AnyOutput, [], 'up', wc2)
  run_and_verify_svn(None, AnyOutput, [],
                     'copy', dir_url + '@1', dir2)
  modify_dir(dir2)

  # New scenario
  # (The dir is already checked out.)
  os.chdir(sbox.wc_dir)
  modify_dir(dir)

  expected_output = None
  expected_disk = None
  expected_status = None
  run_and_verify_update('A', expected_output, expected_disk, expected_status)
  run_and_verify_resolve([dir], '--recursive', '--accept=working', dir)

  os.chdir(saved_cwd)

  def get_status(dir):
    expected_status = svntest.wc.State(dir, {
      ''            : Item(status='  ', wc_rev='2'),
      'A'           : Item(status='A ', wc_rev='-', copied='+'),
      'A/B'         : Item(status='  ', wc_rev='-', copied='+'),
      'A/B/lambda'  : Item(status='D ', wc_rev='1'),
      'A/B/E'       : Item(status='  ', wc_rev='-', copied='+'),
      'A/B/E/alpha' : Item(status='M ', wc_rev='-', copied='+'),
      'A/B/E/beta'  : Item(status='  ', wc_rev='-', copied='+'),
      'A/B/F'       : Item(status='  ', wc_rev='-', copied='+'),
      'A/mu'        : Item(status='  ', wc_rev='-', copied='+'),
      'A/C'         : Item(status='  ', wc_rev='-', copied='+'),
      'A/D'         : Item(status='  ', wc_rev='-', copied='+'),
      'A/D/gamma'   : Item(status='  ', wc_rev='-', copied='+'),
      'A/D/G'       : Item(status='  ', wc_rev='-', copied='+'),
      'A/D/G/pi'    : Item(status='  ', wc_rev='-', copied='+'),
      'A/D/G/rho'   : Item(status='  ', wc_rev='-', copied='+'),
      'A/D/G/tau'   : Item(status='  ', wc_rev='-', copied='+'),
      'A/D/H'       : Item(status='  ', wc_rev='-', copied='+'),
      'A/D/H/chi'   : Item(status='  ', wc_rev='-', copied='+'),
      'A/D/H/omega' : Item(status='  ', wc_rev='-', copied='+'),
      'A/D/H/psi'   : Item(status='  ', wc_rev='-', copied='+'),
      'A/new_file'  : Item(status='A ', wc_rev=0),
      'iota'        : Item(status='  ', wc_rev=2),
    })
    return expected_status

  # The status of the new and old scenarios should be identical...
  expected_status = get_status(wc2)
  svntest.actions.run_and_verify_status(wc2, expected_status)

  # ...except for the revision of the root of the WC and iota, because
  # above 'A' was the target of the update, not the WC root.
  expected_status = get_status(sbox.wc_dir)
  expected_status.tweak('', 'iota', wc_rev=1)
  svntest.actions.run_and_verify_status(sbox.wc_dir, expected_status)

  ### Do we need to do more to confirm we got what we want here?

#----------------------------------------------------------------------
def set_deep_depth_on_target_with_shallow_children(sbox):
  "infinite --set-depth adds shallow children"

  # Regardless of what depth the update target is at, if it has shallow
  # subtrees and we update --set-depth infinity, these shallow subtrees
  # should be populated.
  #
  # See http://svn.haxx.se/dev/archive-2009-04/0344.shtml.

  sbox.build()
  wc_dir = sbox.wc_dir

  # Some paths we'll care about
  A_path = os.path.join(wc_dir, "A")
  B_path = os.path.join(wc_dir, "A", "B")
  D_path = os.path.join(wc_dir, "A", "D")

  # Trim the tree: Set A/B to depth empty and A/D to depth immediates.
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/E'       : Item(status='D '),
    'A/B/lambda'  : Item(status='D '),
    'A/B/F'       : Item(status='D '),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/B/F',
                       'A/B/lambda',
                       'A/B/E',
                       'A/B/E/alpha',
                       'A/B/E/beta')
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/B/F',
                         'A/B/lambda',
                         'A/B/E',
                         'A/B/E/alpha',
                         'A/B/E/beta')
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None,
                                        None, None, 1,
                                        '--set-depth', 'empty',
                                        B_path)
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/G/pi'    : Item(status='D '),
    'A/D/G/rho'   : Item(status='D '),
    'A/D/G/tau'   : Item(status='D '),
    'A/D/H/chi'   : Item(status='D '),
    'A/D/H/omega' : Item(status='D '),
    'A/D/H/psi'   : Item(status='D '),
    })
  expected_status.remove('A/D/G/pi',
                         'A/D/G/rho',
                         'A/D/G/tau',
                         'A/D/H/chi',
                         'A/D/H/omega',
                         'A/D/H/psi')
  expected_disk.remove('A/D/G/pi',
                       'A/D/G/rho',
                       'A/D/G/tau',
                       'A/D/H/chi',
                       'A/D/H/omega',
                       'A/D/H/psi')
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None,
                                        None, None, 1,
                                        '--set-depth', 'immediates',
                                        D_path)

  # Now update A with --set-depth infinity.  All the subtrees we
  # removed above should come back.
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/lambda'  : Item(status='A '),
    'A/B/F'       : Item(status='A '),
    'A/B/E'       : Item(status='A '),
    'A/B/E/alpha' : Item(status='A '),
    'A/B/E/beta'  : Item(status='A '),
    'A/D/G/pi'    : Item(status='A '),
    'A/D/G/rho'   : Item(status='A '),
    'A/D/G/tau'   : Item(status='A '),
    'A/D/H/chi'   : Item(status='A '),
    'A/D/H/omega' : Item(status='A '),
    'A/D/H/psi'   : Item(status='A '),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status,
                                        None, None, None,
                                        None, None, 1,
                                        '--set-depth', 'infinity',
                                        A_path)

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

def update_wc_of_dir_to_rev_not_containing_this_dir(sbox):
  "update wc of dir to rev not containing this dir"

  sbox.build()

  # Create working copy of 'A' directory
  A_url = sbox.repo_url + "/A"
  other_wc_dir = sbox.add_wc_path("other")
  svntest.actions.run_and_verify_svn(None, None, [], "co", A_url, other_wc_dir)
  
  # Delete 'A' directory from repository
  svntest.actions.run_and_verify_svn(None, None, [], "rm", A_url, "-m", "")

  # Try to update working copy of 'A' directory
  svntest.actions.run_and_verify_svn(None, None,
                                     "svn: Target path '/A' does not exist",
                                     "up", other_wc_dir)

#----------------------------------------------------------------------
# Test for issue #3573 'local non-inheritable mergeinfo changes not
# properly merged with updated mergeinfo'
def mergeinfo_updates_merge_with_local_mods(sbox):
  "local mergeinfo changes are merged with updates"

  # Copy A to A_COPY in r2, and make some changes to A_COPY in r3-r6.
  sbox.build()
  wc_dir = sbox.wc_dir
  expected_disk, expected_status = set_up_branch(sbox)

  # Some paths we'll care about
  A_path      = os.path.join(wc_dir, "A")
  A_COPY_path = os.path.join(wc_dir, "A_COPY")

  # Merge -c3 from A to A_COPY at --depth empty, commit as r7.
  ###
  ### No, we are not checking the merge output for these simple
  ### merges.  This is already covered *TO DEATH* in merge_tests.py.
  ###
  svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'merge', '-c3', '--depth', 'empty',
                                     sbox.repo_url + '/A', A_COPY_path)
  svntest.actions.run_and_verify_svn(None, None, [], 'ci', '-m',
                                     'Merge r3 from A to A_COPY at depth empty',
                                     wc_dir)
  # Merge -c5 from A to A_COPY (at default --depth infinity), commit as r8.
  svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'merge', '-c5',
                                     sbox.repo_url + '/A', A_COPY_path)
  svntest.actions.run_and_verify_svn(None, None, [], 'ci', '-m',
                                     'Merge r5 from A to A_COPY', wc_dir)

  # Update WC to r7, repeat merge of -c3 from A to A_COPY but this
  # time do it at --depth infinity.  Confirm that the mergeinfo
  # on A_COPY is no longer inheritable.
  svntest.actions.run_and_verify_svn(None, None, [], 'up', '-r7', wc_dir)
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'merge', '-c3', '--depth', 'infinity',
                                     sbox.repo_url + '/A', A_COPY_path)
  svntest.actions.run_and_verify_svn(None, [A_COPY_path + " - /A:3\n"], [],
                                     'pg', SVN_PROP_MERGEINFO, '-R',
                                     A_COPY_path) 

  # Update the WC (to r8), the mergeinfo on A_COPY should now have both
  # the local mod from the uncommitted merge (/A:3* --> /A:3) and the change
  # brought down by the update (/A:3* --> /A:3*,5) leaving us with /A:3,5.
  ### This was failing because of issue #3573.  The local mergeinfo change
  ### is reverted, leaving '/A:3*,5' on A_COPY.
  svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
  svntest.actions.run_and_verify_svn(None, [A_COPY_path + " - /A:3,5\n"], [],
                                     'pg', SVN_PROP_MERGEINFO, '-R',
                                     A_COPY_path)

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


# list all tests here, starting with None:
test_list = [ None,
              update_binary_file,
              update_binary_file_2,
              update_ignores_added,
              update_to_rev_zero,
              receive_overlapping_same_change,
              update_to_resolve_text_conflicts,
              update_delete_modified_files,
              update_after_add_rm_deleted,
              update_missing,
              update_replace_dir,
              update_single_file,
              prop_update_on_scheduled_delete,
              update_receive_illegal_name,
              update_deleted_missing_dir,
              another_hudson_problem,
              update_deleted_targets,
              new_dir_with_spaces,
              non_recursive_update,
              checkout_empty_dir,
              update_to_deletion,
              update_deletion_inside_out,
              update_schedule_add_dir,
              update_to_future_add,
              nested_in_read_only,
              obstructed_update_alters_wc_props,
              update_xml_unsafe_dir,
              conflict_markers_matching_eol,
              update_eolstyle_handling,
              update_copy_of_old_rev,
              forced_update,
              forced_update_failures,
              XFail(update_wc_on_windows_drive),
              update_wc_with_replaced_file,
              update_with_obstructing_additions,
              update_conflicted,
              SkipUnless(mergeinfo_update_elision,
                         server_has_mergeinfo),
              SkipUnless(update_handles_copyfrom,
                         server_sends_copyfrom_on_update),
              copyfrom_degrades_gracefully,
              SkipUnless(update_handles_copyfrom_with_txdeltas,
                         server_sends_copyfrom_on_update),
              update_copied_from_replaced_and_changed,
              update_copied_and_deleted_prop,
              update_accept_conflicts,
              eof_in_interactive_conflict_resolver,
              update_uuid_changed,
              restarted_update_should_delete_dir_prop,
              tree_conflicts_on_update_1_1,
              tree_conflicts_on_update_1_2,
              tree_conflicts_on_update_2_1,
              tree_conflicts_on_update_2_2,
              XFail(tree_conflicts_on_update_2_3),
              tree_conflicts_on_update_3,
              update_moves_and_modifies_an_edited_file,
              tree_conflict_uc1_update_deleted_tree,
              tree_conflict_uc2_schedule_re_add,
              set_deep_depth_on_target_with_shallow_children,
              update_wc_of_dir_to_rev_not_containing_this_dir,
              mergeinfo_updates_merge_with_local_mods,
             ]

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


### End of file.