#!/usr/bin/env python
#
#  copy_tests.py:  testing the many uses of 'svn cp' and 'svn mv'
#
#  Subversion is a tool for revision control.
#  See http://subversion.apache.org for more information.
#
# ====================================================================
#    Licensed to the Apache Software Foundation (ASF) under one
#    or more contributor license agreements.  See the NOTICE file
#    distributed with this work for additional information
#    regarding copyright ownership.  The ASF licenses this file
#    to you under the Apache License, Version 2.0 (the
#    "License"); you may not use this file except in compliance
#    with the License.  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing,
#    software distributed under the License is distributed on an
#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#    KIND, either express or implied.  See the License for the
#    specific language governing permissions and limitations
#    under the License.
######################################################################

# General modules
import stat, os, re, shutil, logging

logger = logging.getLogger()

# Our testing module
import svntest
from svntest import main
from svntest.main import (
  SVN_PROP_MERGEINFO,
  file_append,
  file_write,
  make_log_msg,
  run_svn,
)

# (abbreviation)
Skip = svntest.testcase.Skip_deco
SkipUnless = svntest.testcase.SkipUnless_deco
XFail = svntest.testcase.XFail_deco
Issues = svntest.testcase.Issues_deco
Issue = svntest.testcase.Issue_deco
Wimp = svntest.testcase.Wimp_deco
Item = svntest.wc.StateItem
exp_noop_up_out = svntest.actions.expected_noop_update_output

#
#----------------------------------------------------------------------
# Helper for wc_copy_replacement and repos_to_wc_copy_replacement
def copy_replace(sbox, wc_copy):
  """Tests for 'R'eplace functionanity for files.

Depending on the value of wc_copy either a working copy (when true)
or a url (when false) copy source is used."""

  sbox.build()
  wc_dir = sbox.wc_dir

  # File scheduled for deletion
  rho_path = sbox.ospath('A/D/G/rho')
  svntest.actions.run_and_verify_svn(None, [], 'rm', rho_path)

  # Status before attempting copies
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/G/rho', status='D ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # The copy shouldn't fail
  if wc_copy:
    pi_src = sbox.ospath('A/D/G/pi')
  else:
    pi_src = sbox.repo_url + '/A/D/G/pi'

  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', pi_src, rho_path)

  # Now commit
  expected_status.tweak('A/D/G/rho', status='R ', copied='+', wc_rev='-')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  expected_status.tweak('A/D/G/rho', status='  ', copied=None,
                        wc_rev='2')
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/G/rho': Item(verb='Replacing'),
    })
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)

# Helper for wc_copy_replace_with_props and
# repos_to_wc_copy_replace_with_props
def copy_replace_with_props(sbox, wc_copy):
  """Tests for 'R'eplace functionanity for files with props.

  Depending on the value of wc_copy either a working copy (when true) or
  a url (when false) copy source is used."""

  sbox.build()
  wc_dir = sbox.wc_dir

  # Use a temp file to set properties with wildcards in their values
  # otherwise Win32/VS2005 will expand them
  prop_path = sbox.ospath('proptmp')
  svntest.main.file_append(prop_path, '*')

  # Set props on file which is copy-source later on
  pi_path = sbox.ospath('A/D/G/pi')
  rho_path = sbox.ospath('A/D/G/rho')
  svntest.actions.run_and_verify_svn(None, [],
                                     'ps', 'phony-prop', '-F',
                                     prop_path, pi_path)
  os.remove(prop_path)
  svntest.actions.run_and_verify_svn(None, [],
                                     'ps', 'svn:eol-style', 'LF', rho_path)

  # Verify props having been set
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/D/G/pi',
                      props={ 'phony-prop': '*' })
  expected_disk.tweak('A/D/G/rho',
                      props={ 'svn:eol-style': 'LF' })

  svntest.actions.verify_disk(wc_dir, expected_disk, True)

  # Commit props
  expected_output = svntest.wc.State(wc_dir, {
    '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('A/D/G/pi',  wc_rev='2')
  expected_status.tweak('A/D/G/rho', wc_rev='2')
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)

  # Bring wc into sync
  svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir)

  # File scheduled for deletion
  svntest.actions.run_and_verify_svn(None, [], 'rm', rho_path)

  # Status before attempting copies
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.tweak('A/D/G/rho', status='D ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # The copy shouldn't fail
  if wc_copy:
    pi_src = sbox.ospath('A/D/G/pi')
  else:
    pi_src = sbox.repo_url + '/A/D/G/pi'

  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', pi_src, rho_path)

  # Verify both content and props have been copied
  if wc_copy:
    props = { 'phony-prop' : '*'}
  else:
    props = { 'phony-prop' : '*'}

  expected_disk.tweak('A/D/G/rho',
                      contents="This is the file 'pi'.\n",
                      props=props)
  svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True)

  # Now commit and verify
  expected_status.tweak('A/D/G/rho', status='R ', copied='+', wc_rev='-')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  expected_status.tweak('A/D/G/rho', status='  ', copied=None,
                        wc_rev='3')
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/G/rho': Item(verb='Replacing'),
    })
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)


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

# (Taken from notes/copy-planz.txt:)
#
#  We have four use cases for 'svn cp' now.
#
#     A. svn cp wc_path1 wc_path2
#
#        This duplicates a path in the working copy, and schedules it
#        for addition with history.
#
#     B. svn cp URL [-r rev]  wc_path
#
#        This "checks out" URL (in REV) into the working copy at
#        wc_path, integrates it, and schedules it for addition with
#        history.
#
#     C. svn cp wc_path URL
#
#        This immediately commits wc_path to URL on the server;  the
#        commit will be an addition with history.  The commit will not
#        change the working copy at all.
#
#     D. svn cp URL1 [-r rev] URL2
#
#        This causes a server-side copy to happen immediately;  no
#        working copy is required.



# TESTS THAT NEED TO BE WRITTEN
#
#  Use Cases A & C
#
#   -- single files, with/without local mods, as both 'cp' and 'mv'.
#        (need to verify commit worked by updating a 2nd working copy
#        to see the local mods)
#
#   -- dir copy, has mixed revisions
#
#   -- dir copy, has local mods (an edit, an add, a delete, and a replace)
#
#   -- dir copy, has mixed revisions AND local mods
#
#   -- dir copy, has mixed revisions AND another previously-made copy!
#      (perhaps done as two nested 'mv' commands!)
#
#  Use Case D
#

#   By the time the copy setup algorithm is complete, the copy
#   operation will have four parts: SRC-DIR, SRC-BASENAME, DST-DIR,
#   DST-BASENAME.  In all cases, SRC-DIR/SRC-BASENAME and DST_DIR must
#   already exist before the operation, but DST_DIR/DST_BASENAME must
#   NOT exist.
#
#   Besides testing things that don't meet the above criteria, we want to
#   also test valid cases:
#
#   - where SRC-DIR/SRC-BASENAME is a file or a dir.
#   - where SRC-DIR (or SRC-DIR/SRC-BASENAME) is a parent/grandparent
#     directory of DST-DIR
#   - where SRC-DIR (or SRC-DIR/SRC-BASENAME) is a child/grandchild
#     directory of DST-DIR
#   - where SRC-DIR (or SRC-DIR/SRC-BASENAME) is not in the lineage
#     of DST-DIR at all



#----------------------------------------------------------------------
@Issue(1091)
def basic_copy_and_move_files(sbox):
  "basic copy and move commands -- on files only"

  sbox.build()
  wc_dir = sbox.wc_dir

  mu_path = sbox.ospath('A/mu')
  iota_path = sbox.ospath('iota')
  rho_path = sbox.ospath('A/D/G/rho')
  D_path = sbox.ospath('A/D')
  alpha_path = sbox.ospath('A/B/E/alpha')
  H_path = sbox.ospath('A/D/H')
  F_path = sbox.ospath('A/B/F')
  alpha2_path = sbox.ospath('A/C/alpha2')

  # Make local mods to mu and rho
  svntest.main.file_append(mu_path, 'appended mu text')
  svntest.main.file_append(rho_path, 'new appended text for rho')

  # Copy rho to D -- local mods
  svntest.actions.run_and_verify_svn(None, [], 'cp', rho_path, D_path)

  # Copy alpha to C -- no local mods, and rename it to 'alpha2' also
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     alpha_path, alpha2_path)

  # Move mu to H -- local mods
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     mu_path, H_path)

  # Move iota to F -- no local mods
  svntest.actions.run_and_verify_svn(None, [], 'mv', iota_path, F_path)

  # Created expected output tree for 'svn ci':
  # We should see four adds, two deletes, and one change in total.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/G/rho' : Item(verb='Sending'),
    'A/D/rho' : Item(verb='Adding'),
    'A/C/alpha2' : Item(verb='Adding'),
    'A/D/H/mu' : Item(verb='Adding'),
    'A/B/F/iota' : Item(verb='Adding'),
    'A/mu' : Item(verb='Deleting'),
    'iota' : Item(verb='Deleting'),
    })

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

  expected_status.add({
    'A/D/rho' : Item(status='  ', wc_rev=2),
    'A/C/alpha2' : Item(status='  ', wc_rev=2),
    'A/D/H/mu' : Item(status='  ', wc_rev=2),
    'A/B/F/iota' : Item(status='  ', wc_rev=2),
    })

  expected_status.remove('A/mu', 'iota')

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

  # Issue 1091, alpha2 would now have the wrong checksum and so a
  # subsequent commit would fail
  svntest.main.file_append(alpha2_path, 'appended alpha2 text')
  expected_output = svntest.wc.State(wc_dir, {
    'A/C/alpha2' : Item(verb='Sending'),
    })
  expected_status.tweak('A/C/alpha2', wc_rev=3)
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)

  # Assure that attempts at local copy and move fail when a log
  # message is provided.
  expected_stderr = \
    ".*Local, non-commit operations do not take a log message"
  svntest.actions.run_and_verify_svn(None, expected_stderr,
                                     'cp', '-m', 'op fails', rho_path, D_path)
  svntest.actions.run_and_verify_svn(None, expected_stderr,
                                     'mv', '-m', 'op fails', rho_path, D_path)


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

# This test passes over ra_local certainly; we're adding it because at
# one time it failed over ra_neon.  Specifically, it failed when
# mod_dav_svn first started sending vsn-rsc-urls as "CR/path", and was
# sending bogus CR/paths for items within copied subtrees.

def receive_copy_in_update(sbox):
  "receive a copied directory during update"

  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)

  # Define a zillion paths in both working copies.
  G_path = sbox.ospath('A/D/G')
  newG_path = sbox.ospath('A/B/newG')

  # Copy directory A/D to A/B/newG
  svntest.actions.run_and_verify_svn(None, [], 'cp', G_path, newG_path)

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

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

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

  # Now update the other working copy; it should receive a full add of
  # the newG directory and its contents.

  # Expected output of update
  expected_output = svntest.wc.State(wc_backup, {
    'A/B/newG' : Item(status='A '),
    'A/B/newG/pi' : Item(status='A '),
    'A/B/newG/rho' : Item(status='A '),
    'A/B/newG/tau' : Item(status='A '),
    })

  # Create expected disk tree for the update.
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/B/newG' : Item(),
    'A/B/newG/pi' : Item("This is the file 'pi'.\n"),
    'A/B/newG/rho' : Item("This is the file 'rho'.\n"),
    'A/B/newG/tau' : Item("This is the file 'tau'.\n"),
    })

  # Create expected status tree for the update.
  expected_status = svntest.actions.get_virginal_state(wc_backup, 2)
  expected_status.add({
    'A/B/newG' : Item(status='  ', wc_rev=2),
    'A/B/newG/pi' : Item(status='  ', wc_rev=2),
    'A/B/newG/rho' : Item(status='  ', wc_rev=2),
    'A/B/newG/tau' : Item(status='  ', wc_rev=2),
    })

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


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

# Regression test for issue #683.  In particular, this bug prevented
# us from running 'svn cp -r N src_URL dst_URL' as a means of
# resurrecting a deleted directory.  Also, the final 'update' at the
# end of this test was uncovering a ghudson 'deleted' edge-case bug.
# (In particular, re-adding G to D, when D already had a 'deleted'
# entry for G.  The entry-merge wasn't overwriting the 'deleted'
# attribute, and thus the newly-added G was ending up disconnected
# from D.)
@Issue(683)
def resurrect_deleted_dir(sbox):
  "resurrect a deleted directory"

  sbox.build()
  wc_dir = sbox.wc_dir
  G_path = sbox.ospath('A/D/G')

  # Delete directory A/D/G, commit that as r2.
  svntest.actions.run_and_verify_svn(None, [], 'rm', '--force',
                                     G_path)

  expected_output = svntest.wc.State(wc_dir, {
    'A/D/G' : Item(verb='Deleting'),
    })

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/D/G')
  expected_status.remove('A/D/G/pi')
  expected_status.remove('A/D/G/rho')
  expected_status.remove('A/D/G/tau')

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

  # Use 'svn cp URL@1 URL' to resurrect the deleted directory, where
  # the two URLs are identical.  This used to trigger a failure.
  url = sbox.repo_url + '/A/D/G'
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     url + '@1', url,
                                     '-m', 'logmsg')

  # For completeness' sake, update to HEAD, and verify we have a full
  # greek tree again, all at revision 3.

  expected_output = svntest.wc.State(wc_dir, {
    '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 '),
    })

  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)

def copy_deleted_dir_into_prefix(sbox):
  "copy a deleted dir to a prefix of its old path"

  sbox.build()
  wc_dir = sbox.wc_dir
  D_path = sbox.ospath('A/D')

  # Delete directory A/D, commit that as r2.
  svntest.actions.run_and_verify_svn(None, [], 'rm', '--force',
                                     D_path)

  expected_output = svntest.wc.State(wc_dir, {
    'A/D' : Item(verb='Deleting'),
    })

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

  # Ok, copy from a deleted URL into a prefix of that URL, this used to
  # result in an assert failing.
  url1 = sbox.repo_url + '/A/D/G'
  url2 = sbox.repo_url + '/A/D'
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     url1 + '@1', url2,
                                     '-m', 'logmsg')

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

# Test that we're enforcing proper 'svn cp' overwrite behavior.  Note
# that svn_fs_copy() will always overwrite its destination if an entry
# by the same name already exists.  However, libsvn_client should be
# doing existence checks to prevent directories from being
# overwritten, and files can't be overwritten because the RA layers
# are doing out-of-dateness checks during the commit.


def no_copy_overwrites(sbox):
  "svn cp URL URL cannot overwrite destination"

  sbox.build()

  wc_dir = sbox.wc_dir

  fileURL1 =  sbox.repo_url + "/A/B/E/alpha"
  fileURL2 =  sbox.repo_url + "/A/B/E/beta"
  dirURL1  =  sbox.repo_url + "/A/D/G"
  dirURL2  =  sbox.repo_url + "/A/D/H"

  # Expect out-of-date failure if 'svn cp URL URL' tries to overwrite a file
  svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput,
                                     'cp', fileURL1, fileURL2,
                                     '-m', 'fooogle')

  # Create A/D/H/G by running 'svn cp ...A/D/G .../A/D/H'
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', dirURL1, dirURL2,
                                     '-m', 'fooogle')

  # Repeat the last command.  It should *fail* because A/D/H/G already exists.
  svntest.actions.run_and_verify_svn(
    None, ".*E160020: Path.*/A/D/H/G' already exists.*",
    'cp', dirURL1, dirURL2,
    '-m', 'fooogle')

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

# Issue 845. WC -> WC copy should not overwrite base text-base
@Issue(845)
def no_wc_copy_overwrites(sbox):
  "svn cp PATH PATH cannot overwrite destination"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  # File simply missing
  tau_path = sbox.ospath('A/D/G/tau')
  os.remove(tau_path)

  # Status before attempting copies
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/G/tau', status='! ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # These copies should fail
  pi_path = sbox.ospath('A/D/G/pi')
  rho_path = sbox.ospath('A/D/G/rho')
  svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput,
                                     'cp', pi_path, rho_path)
  svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput,
                                     'cp', pi_path, tau_path)

  # Status after failed copies should not have changed
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

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

# Takes out working-copy locks for A/B2 and child A/B2/E. At one stage
# during issue 749 the second lock cause an already-locked error.
@Issue(749)
def copy_modify_commit(sbox):
  "copy a tree and modify before commit"

  sbox.build()
  wc_dir = sbox.wc_dir
  B_path = sbox.ospath('A/B')
  B2_path = sbox.ospath('A/B2')

  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     B_path, B2_path)

  alpha_path = sbox.ospath('A/B2/E/alpha')
  svntest.main.file_append(alpha_path, "modified alpha")

  expected_output = svntest.wc.State(wc_dir, {
    'A/B2' : Item(verb='Adding'),
    'A/B2/E/alpha' : Item(verb='Sending'),
    })

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

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

# Issue 591, at one point copying a file from URL to WC didn't copy
# properties.
@Issue(591)
def copy_files_with_properties(sbox):
  "copy files with properties"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Set a property on a file
  rho_path = sbox.ospath('A/D/G/rho')
  svntest.actions.run_and_verify_svn(None, [],
                                     'propset', 'pname', 'pval', rho_path)

  # and commit it
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/G/rho' : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/G/rho', status='  ', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output, expected_status)

  # Set another property, but don't commit it yet
  svntest.actions.run_and_verify_svn(None, [],
                                     'propset', 'pname2', 'pval2', rho_path)

  # WC to WC copy of file with committed and uncommitted properties
  rho_wc_path = sbox.ospath('A/D/G/rho_wc')
  svntest.actions.run_and_verify_svn(None, [],
                                     'copy', rho_path, rho_wc_path)

  # REPOS to WC copy of file with properties
  rho_url_path = sbox.ospath('A/D/G/rho_url')
  rho_url = sbox.repo_url + '/A/D/G/rho'
  svntest.actions.run_and_verify_svn(None, [],
                                     'copy', rho_url, rho_url_path)

  # Properties are not visible in WC status 'A'
  expected_status.add({
    'A/D/G/rho' : Item(status=' M', wc_rev='2'),
    'A/D/G/rho_wc' : Item(status='A ', wc_rev='-', copied='+'),
    'A/D/G/rho_url' : Item(status='A ', wc_rev='-', copied='+'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Check properties explicitly
  svntest.actions.run_and_verify_svn(['pval\n'], [],
                                     'propget', 'pname', rho_wc_path)
  svntest.actions.run_and_verify_svn(['pval2\n'], [],
                                     'propget', 'pname2', rho_wc_path)
  svntest.actions.run_and_verify_svn(['pval\n'], [],
                                     'propget', 'pname', rho_url_path)

  # Commit and properties are visible in status
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/G/rho' : Item(verb='Sending'),
    'A/D/G/rho_wc' : Item(verb='Adding'),
    'A/D/G/rho_url' : Item(verb='Adding'),
    })
  expected_status.tweak('A/D/G/rho', status='  ', wc_rev=3)
  expected_status.remove('A/D/G/rho_wc', 'A/D/G/rho_url')
  expected_status.add({
    'A/D/G/rho_wc' : Item(status='  ', wc_rev=3),
    'A/D/G/rho_url' : Item(status='  ', wc_rev=3),
    })
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output, expected_status)

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

# Issue 918
@Issue(918)
def copy_delete_commit(sbox):
  "copy a tree and delete part of it before commit"

  sbox.build()
  wc_dir = sbox.wc_dir
  B_path = sbox.ospath('A/B')
  B2_path = sbox.ospath('A/B2')

  # copy a tree
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     B_path, B2_path)

  # delete two files
  lambda_path = sbox.ospath('A/B2/lambda')
  alpha_path = sbox.ospath('A/B2/E/alpha')
  svntest.actions.run_and_verify_svn(None, [],
                                     'rm', alpha_path, lambda_path)

  # commit copied tree containing a deleted file
  expected_output = svntest.wc.State(wc_dir, {
    'A/B2' : Item(verb='Adding'),
    'A/B2/lambda' : Item(verb='Deleting'),
    'A/B2/E/alpha' : Item(verb='Deleting'),
    })
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        None)

  # copy a tree
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     sbox.ospath('A/B'),
                                     sbox.ospath('A/B3'))

  # delete a directory
  E_path = sbox.ospath('A/B3/E')
  svntest.actions.run_and_verify_svn(None, [], 'rm', E_path)

  # commit copied tree containing a deleted directory
  expected_output = svntest.wc.State(wc_dir, {
    'A/B3' : Item(verb='Adding'),
    'A/B3/E' : Item(verb='Deleting'),
    })
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        None)


#----------------------------------------------------------------------
@Issues(931,932)
def mv_and_revert_directory(sbox):
  "move and revert a directory"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir
  E_path = sbox.ospath('A/B/E')
  F_path = sbox.ospath('A/B/F')
  new_E_path = os.path.join(F_path, 'E')

  # Issue 931: move failed to lock the directory being deleted
  svntest.actions.run_and_verify_svn(None, [], 'move',
                                     E_path, F_path)
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/B/E/alpha', 'A/B/E/beta', status='D ')
  expected_status.tweak('A/B/E', status='D ', moved_to='A/B/F/E')
  expected_status.add({
    'A/B/F/E' : Item(status='A ', wc_rev='-', copied='+',
                     moved_from='A/B/E'),
    'A/B/F/E/alpha' : Item(status='  ', wc_rev='-', copied='+'),
    'A/B/F/E/beta' : Item(status='  ', wc_rev='-', copied='+'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Issue 932: revert failed to lock the parent directory
  svntest.actions.run_and_verify_svn(None, [], 'revert', '--recursive',
                                     new_E_path)
  expected_status.remove('A/B/F/E', 'A/B/F/E/alpha', 'A/B/F/E/beta')
  expected_status.tweak('A/B/E', moved_to=None)
  svntest.actions.run_and_verify_status(wc_dir, expected_status)


#----------------------------------------------------------------------
# Issue 982.  When copying a file with the executable bit set, the copied
# file should also have its executable bit set.
@Issue(982)
@SkipUnless(svntest.main.is_posix_os)
def copy_preserve_executable_bit(sbox):
  "executable bit should be preserved when copying"

  # Bootstrap
  sbox.build()
  wc_dir = sbox.wc_dir

  # Create two paths
  newpath1 = sbox.ospath('newfile1')
  newpath2 = sbox.ospath('newfile2')

  # Create the first file.
  svntest.main.file_append(newpath1, "a new file")
  svntest.actions.run_and_verify_svn(None, [], 'add', newpath1)

  mode1 = os.stat(newpath1)[stat.ST_MODE]

  # Doing this to get the executable bit set on systems that support
  # that -- the property itself is not the point.
  svntest.actions.run_and_verify_svn(None, [], 'propset',
                                     'svn:executable', 'on', newpath1)

  mode2 = os.stat(newpath1)[stat.ST_MODE]

  if mode1 == mode2:
    logger.warn("setting svn:executable did not change file's permissions")
    raise svntest.Failure

  # Commit the file
  svntest.actions.run_and_verify_svn(None, [], 'ci',
                                     '-m', 'create file and set svn:executable',
                                     wc_dir)

  # Copy the file
  svntest.actions.run_and_verify_svn(None, [], 'cp', newpath1, newpath2)

  mode3 = os.stat(newpath2)[stat.ST_MODE]

  # The mode on the original and copied file should be identical
  if mode2 != mode3:
    logger.warn("permissions on the copied file are not identical to original file")
    raise svntest.Failure

#----------------------------------------------------------------------
# Issue 1029, copy failed with a "working copy not locked" error
@Issue(1029)
def wc_to_repos(sbox):
  "working-copy to repository copy"

  sbox.build()
  wc_dir = sbox.wc_dir

  beta_path = sbox.ospath('A/B/E/beta')
  beta2_url = sbox.repo_url + "/A/B/E/beta2"
  H_path = sbox.ospath('A/D/H')
  H2_url = sbox.repo_url + "/A/D/H2"

  # modify some items to be copied
  svntest.main.file_append(sbox.ospath('A/D/H/omega'),
                           "new otext\n")
  svntest.actions.run_and_verify_svn(None, [], 'propset', 'foo', 'bar',
                                     beta_path)

  # copy a file
  svntest.actions.run_and_verify_svn(None, [], '-m', 'fumble file',
                                     'copy', beta_path, beta2_url)
  # and a directory
  svntest.actions.run_and_verify_svn(None, [], '-m', 'fumble dir',
                                     'copy', H_path, H2_url)
  # copy a file to a directory
  svntest.actions.run_and_verify_svn(None, [], '-m', 'fumble file',
                                     'copy', beta_path, H2_url)

  # update the working copy.  post-update mereinfo elision will remove
  # A/D/H2/beta's mergeinfo, leaving a local mod.
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/E/beta2'  : Item(status='A '),
    'A/D/H2'       : Item(status='A '),
    'A/D/H2/chi'   : Item(status='A '),
    'A/D/H2/omega' : Item(status='A '),
    'A/D/H2/psi'   : Item(status='A '),
    'A/D/H2/beta'  : Item(status='A '),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/D/H/omega',
                      contents="This is the file 'omega'.\nnew otext\n")
  expected_disk.add({
    'A/B/E/beta2'  : Item("This is the file 'beta'.\n"),
    'A/D/H2/chi'   : Item("This is the file 'chi'.\n"),
    'A/D/H2/omega' : Item("This is the file 'omega'.\nnew otext\n"),
    'A/D/H2/psi'   : Item("This is the file 'psi'.\n"),
    'A/D/H2/beta'  : Item("This is the file 'beta'.\n"),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 4)
  expected_status.add({
    'A/B/E/beta'   : Item(status=' M', wc_rev=4),
    'A/D/H/omega'  : Item(status='M ', wc_rev=4),
    'A/B/E/beta2'  : Item(status='  ', wc_rev=4),
    'A/D/H2'       : Item(status='  ', wc_rev=4),
    'A/D/H2/chi'   : Item(status='  ', wc_rev=4),
    'A/D/H2/omega' : Item(status='  ', wc_rev=4),
    'A/D/H2/psi'   : Item(status='  ', wc_rev=4),
    'A/D/H2/beta'  : Item(status='  ', wc_rev=4),
    })
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status)

  # check local property was copied
  svntest.actions.run_and_verify_svn(['bar\n'], [],
                                     'propget', 'foo',
                                     beta_path + "2")

#----------------------------------------------------------------------
# Issue 1090: various use-cases of 'svn cp URL wc' where the
# repositories might be different, or be the same repository.
@Issues(1090, 1444, 3590)
def repos_to_wc(sbox):
  "repository to working-copy copy"

  sbox.build()
  wc_dir = sbox.wc_dir

  # We have a standard repository and working copy.  Now we create a
  # second repository with the same greek tree, but different UUID.
  repo_dir       = sbox.repo_dir
  other_repo_dir, other_repo_url = sbox.add_repo_path('other')
  svntest.main.copy_repos(repo_dir, other_repo_dir, 1, 1)

  # URL->wc copy:
  # copy a file and a directory from the same repository.
  # we should get some scheduled additions *with history*.
  E_url = sbox.repo_url + "/A/B/E"
  pi_url = sbox.repo_url + "/A/D/G/pi"
  pi_path = sbox.ospath('pi')

  svntest.actions.run_and_verify_svn(None, [], 'copy', E_url, wc_dir)
  svntest.actions.run_and_verify_svn(None, [], 'copy', pi_url, wc_dir)

  # Extra test: modify file ASAP to check there was a timestamp sleep
  svntest.main.file_append(pi_path, 'zig\n')

  expected_output = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_output.add({
    'pi' : Item(status='A ', copied='+', wc_rev='-'),
    'E' :  Item(status='A ', copied='+', wc_rev='-'),
    'E/alpha' :  Item(status='  ', copied='+', wc_rev='-'),
    'E/beta'  :  Item(status='  ', copied='+', wc_rev='-'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_output)

  # Modification will only show up if timestamps differ
  exit_code, out, err = svntest.main.run_svn(None, 'diff', pi_path)
  if err or not out:
    logger.warn("diff failed")
    raise svntest.Failure
  for line in out:
    if line == '+zig\n': # Crude check for diff-like output
      break
  else:
    logger.warn("diff output incorrect %s" % out)
    raise svntest.Failure

  # Revert everything and verify.
  svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir)

  svntest.main.safe_rmtree(sbox.ospath('E'))

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

  # URL->wc copy:
  # Copy an empty directory from the same repository, see issue #1444.
  C_url = sbox.repo_url + "/A/C"

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

  expected_output = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_output.add({
    'C' :  Item(status='A ', copied='+', wc_rev='-'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_output)

  # Revert everything and verify.
  svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir)

  svntest.main.safe_rmtree(sbox.ospath('C'))

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

  # URL->wc copy:
  # copy a file and a directory from a foreign repository.
  # we should get some scheduled additions *without history*.
  E_url = other_repo_url + "/A/B/E"
  pi_url = other_repo_url + "/A/D/G/pi"

  # Finally, for 1.8 we allow this copy to succeed.
  expected_output = svntest.verify.UnorderedOutput([
    '--- Copying from foreign repository URL \'%s\':\n' % E_url,
    'A         %s\n' % sbox.ospath('E'),
    'A         %s\n' % sbox.ospath('E/beta'),
    'A         %s\n' % sbox.ospath('E/alpha'),
  ])
  svntest.actions.run_and_verify_svn(expected_output, [],
                                     'copy', E_url, wc_dir)

  expected_output = [
    '--- Copying from foreign repository URL \'%s\':\n' % pi_url,
    'A         %s\n' % sbox.ospath('pi'),
  ]
  svntest.actions.run_and_verify_svn(expected_output, [],
                                     'copy', pi_url, wc_dir)

  expected_output = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_output.add({
    'pi'            : Item(status='A ', wc_rev='0'),
    'E'             : Item(status='A ', wc_rev='0'),
    'E/beta'        : Item(status='A ', wc_rev='0'),
    'E/alpha'       : Item(status='A ', wc_rev='0'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_output)

  # Revert everything and verify.
  svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir)
  expected_output = svntest.actions.get_virginal_state(wc_dir, 1)

  # URL->wc copy:
  # Copy a directory to a pre-existing WC directory.
  # The source directory should be copied *under* the target directory.
  B_url = sbox.repo_url + "/A/B"
  D_dir = sbox.ospath('A/D')

  svntest.actions.run_and_verify_svn(None, [],
                                     'copy', B_url, D_dir)

  expected_output = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_output.add({
    'A/D/B'         : Item(status='A ', copied='+', wc_rev='-'),
    'A/D/B/lambda'  : Item(status='  ', copied='+', wc_rev='-'),
    'A/D/B/E'       : Item(status='  ', copied='+', wc_rev='-'),
    'A/D/B/E/beta'  : Item(status='  ', copied='+', wc_rev='-'),
    'A/D/B/E/alpha' : Item(status='  ', copied='+', wc_rev='-'),
    'A/D/B/F'       : Item(status='  ', copied='+', wc_rev='-'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_output)

  # Validate the mergeinfo of the copy destination (we expect none)
  svntest.actions.run_and_verify_svn([], '.*W200017: Property.*not found',
                                     'propget', SVN_PROP_MERGEINFO,
                                     os.path.join(D_dir, 'B'))

#----------------------------------------------------------------------
def foreign_repos_to_wc(sbox):
  "foreign repository to WC copy"

  sbox.build()
  wc_dir = sbox.wc_dir

  def move_url(repo_url, source, dest):
    svntest.main.run_svn(False, 'move', '-m', svntest.main.make_log_msg(),
                         repo_url + '/' + source,
                         repo_url + '/' + dest)

  # Scenarios:
  # (parent-path-under-'A/', base-name, child-paths, mergeinfo-inheritance)
  scenarios = [
    ('B',   'E',   ['alpha','beta'], 'explicit'),
    ('B',   'F',   [],               'inherited'),
    ('D/G', 'pi',  [],               'explicit'),
    ('D/G', 'rho', [],               'inherited'),
  ]

  # Add some mergeinfo, which should be discarded by a foreign repo copy.
  # On each path of interest, add either explicit or inherited mergeinfo:
  # the implementation handles these cases differently.
  # (We commit these initially in the original repo just for convenience: as
  # we already have a WC. Really they only need to be in the 'other' repo.)
  for parent, name, children, mi_inheritance in scenarios:
    if mi_inheritance == 'explicit':
      sbox.simple_propset(SVN_PROP_MERGEINFO,
                          '/branch/' + name + ':1', 'A/' + parent + '/' + name)
    else:
      sbox.simple_propset(SVN_PROP_MERGEINFO,
                          '/branch/' + name + ':1', 'A/' + parent)
  sbox.simple_commit()

  # We have a standard repository and working copy.  Now we create a
  # second repository with the same greek tree, but different UUID.
  repo_dir       = sbox.repo_dir
  other_repo_dir, other_repo_url = sbox.add_repo_path('other')
  svntest.main.copy_repos(repo_dir, other_repo_dir, 2, 1)
  move_url(other_repo_url, 'A', 'A2')
  move_url(other_repo_url, 'A2', 'A3')

  # URL->wc copy:
  # copy a file and a directory from a foreign repository.
  # we should get some scheduled additions *without history*.
  peg_rev = '3'
  op_rev = '2'

  for parent, name, children, mi_inheritance in scenarios:
    src_url = other_repo_url + '/A2/' + parent + '/' + name
    src_url_resolved = src_url.replace('/A2/', '/A/')

    expected_output = svntest.verify.UnorderedOutput([
      '--- Copying from foreign repository URL \'%s\':\n' % src_url_resolved,
      'A         %s\n' % sbox.ospath(name),
    ] + [
      'A         %s\n' % sbox.ospath(name + '/' + child)
      for child in children
    ])
    svntest.actions.run_and_verify_svn(expected_output, [],
                                       'copy', '-r' + op_rev,
                                       src_url + '@' + peg_rev,
                                       wc_dir)

    # Validate the mergeinfo of the copy destination (we expect none)
    svntest.actions.run_and_verify_svn([], '.*W200017: Property.*not found',
                                       'propget', SVN_PROP_MERGEINFO,
                                       sbox.ospath(name))

#----------------------------------------------------------------------
# Issue 1084: ra_svn move/copy bug
@Issue(1084)
def copy_to_root(sbox):
  'copy item to root of repository'

  sbox.build()
  wc_dir = sbox.wc_dir

  root = sbox.repo_url
  mu = root + '/A/mu'

  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     '-m', '',
                                     mu, root)

  # Update to HEAD, and check to see if the files really were copied in the
  # repo

  expected_output = svntest.wc.State(wc_dir, {
    'mu': Item(status='A '),
    })

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'mu': Item(contents="This is the file 'mu'.\n")
    })

  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'mu': Item(status='  ', wc_rev=2),
    })

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

#----------------------------------------------------------------------
@Issue(1367)
def url_copy_parent_into_child(sbox):
  "copy URL URL/subdir"

  sbox.build()
  wc_dir = sbox.wc_dir

  B_url = sbox.repo_url + "/A/B"
  F_url = sbox.repo_url + "/A/B/F"

  # Issue 1367 parent/child URL-to-URL was rejected.
  svntest.actions.run_and_verify_svn(['Committing transaction...\n',
                                      'Committed revision 2.\n'], [],
                                     'cp',
                                     '-m', 'a can of worms',
                                     B_url, F_url)

  # Do an update to verify the copy worked
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/F/B'         : Item(status='A '),
    'A/B/F/B/E'       : Item(status='A '),
    'A/B/F/B/E/alpha' : Item(status='A '),
    'A/B/F/B/E/beta'  : Item(status='A '),
    'A/B/F/B/F'       : Item(status='A '),
    'A/B/F/B/lambda'  : Item(status='A '),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/B/F/B'         : Item(),
    'A/B/F/B/E'       : Item(),
    'A/B/F/B/E/alpha' : Item("This is the file 'alpha'.\n"),
    'A/B/F/B/E/beta'  : Item("This is the file 'beta'.\n"),
    'A/B/F/B/F'       : Item(),
    'A/B/F/B/lambda'  : Item("This is the file 'lambda'.\n"),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'A/B/F/B'         : Item(status='  ', wc_rev=2),
    'A/B/F/B/E'       : Item(status='  ', wc_rev=2),
    'A/B/F/B/E/alpha' : Item(status='  ', wc_rev=2),
    'A/B/F/B/E/beta'  : Item(status='  ', wc_rev=2),
    'A/B/F/B/F'       : Item(status='  ', wc_rev=2),
    'A/B/F/B/lambda'  : Item(status='  ', wc_rev=2),
    })
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status)

#----------------------------------------------------------------------
@Issue(1367)
def wc_copy_parent_into_child(sbox):
  "copy WC URL/subdir"

  sbox.build(create_wc = False)
  wc_dir = sbox.wc_dir

  B_url = sbox.repo_url + "/A/B"
  F_B_url = sbox.repo_url + "/A/B/F/B"

  # Want a smaller WC
  svntest.main.safe_rmtree(wc_dir)
  svntest.actions.run_and_verify_svn(None, [],
                                     'checkout',
                                     B_url, wc_dir)

  # Issue 1367: A) copying '.' to URL failed with a parent/child
  # error, and also B) copying root of a working copy attempted to
  # lock the non-working copy parent directory.
  was_cwd = os.getcwd()
  os.chdir(wc_dir)

  svntest.actions.run_and_verify_svn(['Adding copy of        .\n',
                                      'Committing transaction...\n',
                                      'Committed revision 2.\n'], [],
                                     'cp',
                                     '-m', 'a larger can',
                                     '.', F_B_url)

  os.chdir(was_cwd)

  # Do an update to verify the copy worked
  expected_output = svntest.wc.State(wc_dir, {
    'F/B'         : Item(status='A '),
    'F/B/E'       : Item(status='A '),
    'F/B/E/alpha' : Item(status='A '),
    'F/B/E/beta'  : Item(status='A '),
    'F/B/F'       : Item(status='A '),
    'F/B/lambda'  : Item(status='A '),
    })
  expected_disk = svntest.wc.State('', {
    'E'           : Item(),
    'E/alpha'     : Item("This is the file 'alpha'.\n"),
    'E/beta'      : Item("This is the file 'beta'.\n"),
    'F'           : Item(),
    'lambda'      : Item("This is the file 'lambda'.\n"),
    'F/B'         : Item(),
    'F/B/E'       : Item(),
    'F/B/E/alpha' : Item("This is the file 'alpha'.\n"),
    'F/B/E/beta'  : Item("This is the file 'beta'.\n"),
    'F/B/F'       : Item(),
    'F/B/lambda'  : Item("This is the file 'lambda'.\n"),
    })
  expected_status = svntest.wc.State(wc_dir, {
    ''            : Item(status='  ', wc_rev=2),
    'E'           : Item(status='  ', wc_rev=2),
    'E/alpha'     : Item(status='  ', wc_rev=2),
    'E/beta'      : Item(status='  ', wc_rev=2),
    'F'           : Item(status='  ', wc_rev=2),
    'lambda'      : Item(status='  ', wc_rev=2),
    'F/B'         : Item(status='  ', wc_rev=2),
    'F/B/E'       : Item(status='  ', wc_rev=2),
    'F/B/E/alpha' : Item(status='  ', wc_rev=2),
    'F/B/E/beta'  : Item(status='  ', wc_rev=2),
    'F/B/F'       : Item(status='  ', wc_rev=2),
    'F/B/lambda'  : Item(status='  ', wc_rev=2),
    })
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status)

#----------------------------------------------------------------------
# Issue 1419: at one point ra_neon->get_uuid() was failing on a
# non-existent public URL, which prevented us from resurrecting files
# (svn cp -rOLD URL wc).
@Issue(1419)
def resurrect_deleted_file(sbox):
  "resurrect a deleted file"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Delete a file in the repository via immediate commit
  rho_url = sbox.repo_url + '/A/D/G/rho'
  svntest.actions.run_and_verify_svn(None, [],
                                     'rm', rho_url, '-m', 'rev 2')

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

  # repos->wc copy, to resurrect deleted file.
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', rho_url + '@1', wc_dir)

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

#-------------------------------------------------------------
# Regression tests for Issue #1297:
# svn diff failed after a repository to WC copy of a single file
# This test checks just that.
@Issue(1297)
def diff_repos_to_wc_copy(sbox):
  "copy file from repos to working copy and run diff"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  iota_repos_path = sbox.repo_url + '/iota'
  target_wc_path = sbox.ospath('new_file')

  # Copy a file from the repository to the working copy.
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     iota_repos_path, target_wc_path)

  # Run diff.
  svntest.actions.run_and_verify_svn(None, [], 'diff', wc_dir)


#-------------------------------------------------------------
@Issue(1473)
def repos_to_wc_copy_eol_keywords(sbox):
  "repos->WC copy with keyword or eol property set"

  # See issue #1473: repos->wc copy would seg fault if svn:keywords or
  # svn:eol were set on the copied file, because we'd be querying an
  # entry for keyword values when the entry was still null (because
  # not yet been fully installed in the wc).

  sbox.build()
  wc_dir = sbox.wc_dir

  iota_repos_path = sbox.repo_url + '/iota'
  iota_wc_path = sbox.ospath('iota')
  target_wc_path = sbox.ospath('new_file')

  # Modify iota to make it checkworthy.
  svntest.main.file_write(iota_wc_path,
                          "Hello\nSubversion\n$LastChangedRevision$\n",
                          "ab")

  svntest.actions.run_and_verify_svn(None, [],
                                     'propset', 'svn:eol-style',
                                     'CRLF', iota_wc_path)

  svntest.actions.run_and_verify_svn(None, [],
                                     'propset', 'svn:keywords',
                                     'Rev', iota_wc_path)

  svntest.actions.run_and_verify_svn(None, [],
                                     'commit', '-m', 'log msg',
                                     wc_dir)

  # Copy a file from the repository to the working copy.
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     iota_repos_path, target_wc_path)

  # The original bug was that the copy would seg fault.  So we test
  # that the copy target exists now; if it doesn't, it's probably
  # because of the segfault.  Note that the crash would be independent
  # of whether there are actually any line breaks or keywords in the
  # file's contents -- the mere existence of the property would
  # trigger the bug.
  if not os.path.exists(target_wc_path):
    raise svntest.Failure

  # Okay, if we got this far, we might as well make sure that the
  # translations/substitutions were done correctly:
  f = open(target_wc_path, "rb")
  raw_contents = f.read()
  f.seek(0, 0)
  line_contents = f.readlines()
  f.close()

  if re.match(b'[^\\r]\\n', raw_contents):
    raise svntest.Failure

  if not re.match(b'.*\$LastChangedRevision:\s*\d+\s*\$', line_contents[3]):
    raise svntest.Failure

#-------------------------------------------------------------
# Regression test for revision 7331, with commented-out parts for a further
# similar bug.

def revision_kinds_local_source(sbox):
  "revision-kind keywords with non-URL source"

  sbox.build()
  wc_dir = sbox.wc_dir

  mu_path = sbox.ospath('A/mu')

  # Make a file with different content in each revision and WC; BASE != HEAD.
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu' : Item(verb='Sending'), })
  svntest.main.file_append(mu_path, "New r2 text.\n")
  svntest.actions.run_and_verify_commit(wc_dir, expected_output, None)
  svntest.main.file_append(mu_path, "New r3 text.\n")
  svntest.actions.run_and_verify_commit(wc_dir, expected_output, None)
  svntest.actions.run_and_verify_svn(None, [], 'up', '-r2', mu_path)
  svntest.main.file_append(mu_path, "Working copy.\n")

  r1 = "This is the file 'mu'.\n"
  r2 = r1 + "New r2 text.\n"
  r3 = r2 + "New r3 text.\n"
  rWC = r2 + "Working copy.\n"

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/mu', contents=rWC)

  # Test the various revision-kind keywords, and none.
  sub_tests = [ ('file0', 2, rWC, None),
                ('file1', 3, r3, 'HEAD'),
                ('file2', 2, r2, 'BASE'),
                # ('file3', 2, r2, 'COMMITTED'),
                # ('file4', 1, r1, 'PREV'),
              ]

  for dst, from_rev, text, peg_rev in sub_tests:
    dst_path = os.path.join(wc_dir, dst)
    if peg_rev is None:
      svntest.actions.run_and_verify_svn(None, [], "copy",
                                         mu_path, dst_path)
    else:
      svntest.actions.run_and_verify_svn(None, [], "copy",
                                         mu_path + "@" + peg_rev, dst_path)
    expected_disk.add({ dst: Item(contents=text) })

    # Check that the copied-from revision == from_rev.
    exit_code, output, errput = svntest.main.run_svn(None, "info", dst_path)
    for line in output:
      if line.rstrip() == "Copied From Rev: " + str(from_rev):
        break
    else:
      logger.warn("%s should have been copied from revision %s" % (dst, from_rev))
      raise svntest.Failure

  # Check that the new files have the right contents
  svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True)


#-------------------------------------------------------------
# Regression test for issue 1581.
@Issue(1581)
def copy_over_missing_file(sbox):
  "copy over a missing file"
  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  mu_path = sbox.ospath('A/mu')
  iota_path = sbox.ospath('iota')
  iota_url = sbox.repo_url + "/iota"

  # Make the target missing.
  os.remove(mu_path)

  # Try both wc->wc copy and repos->wc copy, expect failures:
  svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput,
                                     'cp', iota_path, mu_path)

  svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput,
                                    'cp', iota_url, mu_path)

  # Make sure that the working copy is not corrupted:
  expected_disk = svntest.main.greek_state.copy()
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_output = svntest.wc.State(wc_dir, {'A/mu' : Item(verb='Restored')})
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status)



#----------------------------------------------------------------------
#  Regression test for issue 1634
@Issue(1634)
def repos_to_wc_1634(sbox):
  "copy a deleted directory back from the repos"

  sbox.build()
  wc_dir = sbox.wc_dir

  # First delete a subdirectory and commit.
  E_path = sbox.ospath('A/B/E')
  svntest.actions.run_and_verify_svn(None, [], 'delete', E_path)
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/E' : Item(verb='Deleting'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/B/E', 'A/B/E/alpha', 'A/B/E/beta')
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)

  # Now copy the directory back.
  E_url = sbox.repo_url + "/A/B/E@1"
  svntest.actions.run_and_verify_svn(None, [],
                                     'copy', E_url, E_path)
  expected_status.add({
    'A/B/E'       :  Item(status='A ', copied='+', wc_rev='-'),
    'A/B/E/alpha' :  Item(status='  ', copied='+', wc_rev='-'),
    'A/B/E/beta'  :  Item(status='  ', copied='+', wc_rev='-'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir)
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'A/B/E'       :  Item(status='A ', copied='+', wc_rev='-'),
    'A/B/E/alpha' :  Item(status='  ', copied='+', wc_rev='-'),
    'A/B/E/beta'  :  Item(status='  ', copied='+', wc_rev='-'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

#----------------------------------------------------------------------
#  Regression test for issue 1814
@Issue(1814)
def double_uri_escaping_1814(sbox):
  "check for double URI escaping in svn ls -R"

  sbox.build(create_wc = False)

  base_url = sbox.repo_url + '/base'

  # rev. 2
  svntest.actions.run_and_verify_svn(None, [],
                                     'mkdir', '-m', 'mybase',
                                     base_url)

  orig_url = base_url + '/foo%20bar'

  # rev. 3
  svntest.actions.run_and_verify_svn(None, [],
                                     'mkdir', '-m', 'r1',
                                     orig_url)
  orig_rev = 3

  # rev. 4
  new_url = base_url + '/foo_bar'
  svntest.actions.run_and_verify_svn(None, [],
                                     'mv', '-m', 'r2',
                                     orig_url, new_url)

  # This had failed with ra_neon because "foo bar" would be double-encoded
  # "foo bar" ==> "foo%20bar" ==> "foo%2520bar"
  svntest.actions.run_and_verify_svn(None, [],
                                     'ls', ('-r'+str(orig_rev)),
                                     '-R', base_url)


#----------------------------------------------------------------------
#  Regression test for issues 2404
@Issue(2404)
def wc_to_wc_copy_between_different_repos(sbox):
  "wc to wc copy attempts between different repos"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  sbox2 = sbox.clone_dependent()
  sbox2.build()
  wc2_dir = sbox2.wc_dir

  # Attempt a copy between different repositories.
  exit_code, out, err = svntest.main.run_svn(1, 'cp',
                                             os.path.join(wc2_dir, 'A'),
                                             sbox.ospath('A/B'))
  for line in err:
    if line.find("it is not from repository") != -1:
      break
  else:
    raise svntest.Failure

#----------------------------------------------------------------------
#  Regression test for issues 2101, 2020 and 3776
@Issues(2101,2020,3776)
def wc_to_wc_copy_deleted(sbox):
  "wc to wc copy with presence=not-present items"

  sbox.build()
  wc_dir = sbox.wc_dir

  B_path = sbox.ospath('A/B')
  B2_path = sbox.ospath('A/B2')

  # Schedule for delete
  svntest.actions.run_and_verify_svn(None, [], 'rm',
                                     os.path.join(B_path, 'E', 'alpha'),
                                     os.path.join(B_path, 'lambda'),
                                     os.path.join(B_path, 'F'))
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/B/E/alpha', 'A/B/lambda', 'A/B/F', status='D ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Commit to get state not-present
  expected_status.remove('A/B/E/alpha', 'A/B/lambda', 'A/B/F')
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/E/alpha' : Item(verb='Deleting'),
    'A/B/lambda'  : Item(verb='Deleting'),
    'A/B/F'       : Item(verb='Deleting'),
    })
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)

  # Copy including stuff in state not-present
  svntest.actions.run_and_verify_svn(None, [], 'copy', B_path, B2_path)
  expected_status.add({
    'A/B2'         : Item(status='A ', wc_rev='-', copied='+'),
    'A/B2/E'       : Item(status='  ', wc_rev='-', copied='+'),
    'A/B2/E/beta'  : Item(status='  ', wc_rev='-', copied='+'),
    'A/B2/E/alpha' : Item(status='D ', wc_rev='-', copied='+'),
    'A/B2/lambda'  : Item(status='D ', wc_rev='-', copied='+'),
    'A/B2/F'       : Item(status='D ', wc_rev='-', copied='+'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Reverting the copied not-present is a no-op.
  svntest.main.run_svn(1, 'revert', os.path.join(B2_path, 'F'))
  svntest.main.run_svn(1, 'revert', os.path.join(B2_path, 'lambda'))
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Revert the entire copy including the schedule not-present bits
  svntest.actions.run_and_verify_svn(None, [], 'revert', '--recursive',
                                     B2_path)
  expected_status.remove('A/B2',
                         'A/B2/E',
                         'A/B2/E/beta',
                         'A/B2/E/alpha',
                         'A/B2/lambda',
                         'A/B2/F')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)
  svntest.main.safe_rmtree(B2_path)

  # Copy again and commit
  svntest.actions.run_and_verify_svn(None, [], 'copy', B_path, B2_path)

  expected_status.add({
    'A/B2'              : Item(status='A ', copied='+', wc_rev='-'),
    'A/B2/lambda'       : Item(status='D ', copied='+', wc_rev='-'),
    'A/B2/F'            : Item(status='D ', copied='+', wc_rev='-'),
    'A/B2/E'            : Item(status='  ', copied='+', wc_rev='-'),
    'A/B2/E/alpha'      : Item(status='D ', copied='+', wc_rev='-'),
    'A/B2/E/beta'       : Item(status='  ', copied='+', wc_rev='-')
  })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  expected_status.remove('A/B2/lambda', 'A/B2/F', 'A/B2/E/alpha')
  expected_status.tweak('A/B2', 'A/B2/E', 'A/B2/E/beta', status='  ',
                        copied=None, wc_rev=3)
  expected_output = svntest.wc.State(wc_dir, {
    'A/B2'         : Item(verb='Adding'),
    'A/B2/E/alpha' : Item(verb='Deleting'),
    'A/B2/lambda'  : Item(verb='Deleting'),
    'A/B2/F'       : Item(verb='Deleting'),
    })

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

#----------------------------------------------------------------------
# Test for copy into a non-existent URL path
def url_to_non_existent_url_path(sbox):
  "svn cp src-URL non-existent-URL-path"

  sbox.build(create_wc = False)

  dirURL1 = sbox.repo_url + "/A/B/E"
  dirURL2 = sbox.repo_url + "/G/C/E/I"

  # Look for both possible versions of the error message, as the DAV
  # error is worded differently from that of other RA layers.
  msg = ".*: (" + \
        "|".join(["Path 'G(/C/E)?' not present",
                  ".*G(/C/E)?' path not found",
                  "File not found.*'/G/C/E/I'",
                  ]) + ")"

  # Expect failure on 'svn cp SRC DST' where one or more ancestor
  # directories of DST do not exist
  exit_code, out, err = svntest.main.run_svn(1, 'cp', dirURL1, dirURL2,
                                             '-m', 'fooogle')
  for err_line in err:
    if re.match (msg, err_line):
      break
  else:
    logger.warn("message \"%s\" not found in error output: %s" % (msg, err))
    raise svntest.Failure


#----------------------------------------------------------------------
# Test for a copying (URL to URL) an old rev of a deleted file in a
# deleted directory.
def non_existent_url_to_url(sbox):
  "svn cp oldrev-of-deleted-URL URL"

  sbox.build(create_wc = False)

  adg_url = sbox.repo_url + '/A/D/G'
  pi_url = sbox.repo_url + '/A/D/G/pi'
  new_url = sbox.repo_url + '/newfile'

  svntest.actions.run_and_verify_svn(None, [],
                                     'delete',
                                     adg_url, '-m', '')

  svntest.actions.run_and_verify_svn(None, [],
                                     'copy',
                                     pi_url + '@1', new_url,
                                     '-m', '')

#----------------------------------------------------------------------
def old_dir_url_to_url(sbox):
  "test URL to URL copying edge case"

  sbox.build(create_wc = False)

  adg_url = sbox.repo_url + '/A/D/G'
  pi_url = sbox.repo_url + '/A/D/G/pi'
  iota_url = sbox.repo_url + '/iota'
  new_url = sbox.repo_url + '/newfile'

  # Delete a directory
  svntest.actions.run_and_verify_svn(None, [],
                                     'delete',
                                     adg_url, '-m', '')

  # Copy a file to where the directory used to be
  svntest.actions.run_and_verify_svn(None, [],
                                     'copy',
                                     iota_url, adg_url,
                                     '-m', '')

  # Try copying a file that was in the deleted directory that is now a
  # file
  svntest.actions.run_and_verify_svn(None, [],
                                     'copy',
                                     pi_url + '@1', new_url,
                                     '-m', '')



#----------------------------------------------------------------------
# Test fix for issue 2224 - copying wc dir to itself causes endless
# recursion
@Issue(2224)
def wc_copy_dir_to_itself(sbox):
  "copy wc dir to itself"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir
  dnames = ['A','A/B']

  for dirname in dnames:
    dir_path = os.path.join(wc_dir, dirname)

    # try to copy dir to itself
    svntest.actions.run_and_verify_svn([],
                                       '.*Cannot copy .* into its own child.*',
                                       'copy', dir_path, dir_path)


#----------------------------------------------------------------------
@Issue(2153)
def mixed_wc_to_url(sbox):
  "copy a complex mixed-rev wc"

  # For issue 2153.
  #
  # Copy a mixed-revision wc (that also has some uncommitted local
  # mods, and an entry marked as 'deleted') to a URL.  Make sure the
  # copy gets the uncommitted mods, and does not contain the deleted
  # file.

  sbox.build()

  wc_dir = sbox.wc_dir
  Z_url = sbox.repo_url + '/A/D/Z'
  Z2_url = sbox.repo_url + '/A/D/Z2'
  G_path = sbox.ospath('A/D/G')
  B_path = sbox.ospath('A/B')
  X_path = sbox.ospath('A/D/G/X')
  Y_path = sbox.ospath('A/D/G/Y')
  E_path = sbox.ospath('A/D/G/X/E')
  alpha_path = sbox.ospath('A/D/G/X/E/alpha')
  pi_path = sbox.ospath('A/D/G/pi')
  rho_path = sbox.ospath('A/D/G/rho')

  # Remove A/D/G/pi, then commit that removal.
  svntest.actions.run_and_verify_svn(None, [], 'rm', pi_path)
  svntest.actions.run_and_verify_svn(None, [],
                                     'ci', '-m', "Delete pi.", wc_dir)

  # Make a modification to A/D/G/rho, then commit that modification.
  svntest.main.file_append(rho_path, "\nFirst modification to rho.\n")
  svntest.actions.run_and_verify_svn(None, [],
                                     'ci', '-m', "Modify rho.", wc_dir)

  # Make another modification to A/D/G/rho, but don't commit it.
  svntest.main.file_append(rho_path, "Second modification to rho.\n")

  # Copy into the source, delete part of the copy, add a non-copied directory
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', B_path, X_path)
  svntest.actions.run_and_verify_svn(None, [],
                                     'rm', alpha_path)
  svntest.actions.run_and_verify_svn(None, [],
                                     'mkdir', Y_path)

  # Now copy local A/D/G to create new directory A/D/Z the repository.

  expected_status = svntest.wc.State(G_path, {
    ''                  : Item(status='  ', wc_rev='1'),
    'X'                 : Item(status='A ', copied='+', wc_rev='-'),
    'X/F'               : Item(status='  ', copied='+', wc_rev='-'),
    'X/E'               : Item(status='  ', copied='+', wc_rev='-'),
    'X/E/alpha'         : Item(status='D ', copied='+', wc_rev='-'),
    'X/E/beta'          : Item(status='  ', copied='+', wc_rev='-'),
    'X/lambda'          : Item(status='  ', copied='+', wc_rev='-'),
    'Y'                 : Item(status='A ', wc_rev='-'),
    'rho'               : Item(status='M ', wc_rev='3'),
    'tau'               : Item(status='  ', wc_rev='1'),
  })

  svntest.actions.run_and_verify_status(G_path, expected_status)

  expected_output = svntest.verify.UnorderedOutput([
      'Adding copy of        %s\n' % sbox.ospath('A/D/G'),
      'Adding copy of        %s\n' % sbox.ospath('A/D/G/X'),
      'Deleting copy of      %s\n' % sbox.ospath('A/D/G/X/E/alpha'),
      'Adding copy of        %s\n' % sbox.ospath('A/D/G/Y'),
      'Deleting copy of      %s\n' % sbox.ospath('A/D/G/pi'),
      'Replacing copy of     %s\n' % sbox.ospath('A/D/G/rho'),
      'Transmitting file data .done\n',
      'Committing transaction...\n',
      'Committed revision 4.\n',
  ])
  svntest.actions.run_and_verify_svn(expected_output, [],
                                     'cp', '-m', "Make a copy.",
                                     G_path, Z_url)
  expected_output = svntest.verify.UnorderedOutput([
    'A + A/D/Z/\n',
    '    (from A/D/G/:r1)\n',
    'A + A/D/Z/X/\n',
    '    (from A/B/:r1)\n',
    'D   A/D/Z/X/E/alpha\n',
    'A   A/D/Z/Y/\n',
    'D   A/D/Z/pi\n',
    'D   A/D/Z/rho\n',
    'A + A/D/Z/rho\n',
    '    (from A/D/G/rho:r3)\n',
    ])
  svntest.actions.run_and_verify_svnlook(expected_output, [],
                                         'changed', sbox.repo_dir,
                                         '--copy-info')

  # Copy from copied source
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', '-m', "Make a copy.",
                                     E_path, Z2_url)
  expected_output = svntest.verify.UnorderedOutput([
    'A + A/D/Z2/\n',
    '    (from A/B/E/:r1)\n',
    'D   A/D/Z2/alpha\n',
    ])
  svntest.actions.run_and_verify_svnlook(expected_output, [],
                                         'changed', sbox.repo_dir,
                                         '--copy-info')

  # Check out A/D/Z.  If it has pi, that's a bug; or if its rho does
  # not have the second local mod, that's also a bug.
  svntest.main.safe_rmtree(wc_dir)
  svntest.actions.run_and_verify_svn(None, [],
                                     'co', Z_url, wc_dir)

  if os.path.exists(sbox.ospath('pi')):
    raise svntest.Failure("Path 'pi' exists but should be gone.")

  fp = open(sbox.ospath('rho'), 'r')
  found_it = 0
  for line in fp.readlines():
    if re.match("^Second modification to rho.", line):
      found_it = 1
  if not found_it:
    raise svntest.Failure("The second modification to rho didn't make it.")


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

# Issue 845 and 1516: WC replacement of files requires
# a second text-base and prop-base
@Issues(845,1516)
def wc_copy_replacement(sbox):
  "svn cp PATH PATH replace file"

  copy_replace(sbox, 1)

def wc_copy_replace_with_props(sbox):
  "svn cp PATH PATH replace file with props"

  copy_replace_with_props(sbox, 1)


def repos_to_wc_copy_replacement(sbox):
  "svn cp URL PATH replace file"

  copy_replace(sbox, 0)

def repos_to_wc_copy_replace_with_props(sbox):
  "svn cp URL PATH replace file with props"

  copy_replace_with_props(sbox, 0)

# See also delete_replace_delete() which does the same for a directory.
def delete_replaced_file(sbox):
  "delete a file scheduled for replacement"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  # File scheduled for deletion.
  rho_path = sbox.ospath('A/D/G/rho')
  svntest.actions.run_and_verify_svn(None, [], 'rm', rho_path)

  # Status before attempting copies
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/G/rho', status='D ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Copy 'pi' over 'rho' with history.
  pi_src = sbox.ospath('A/D/G/pi')
  svntest.actions.run_and_verify_svn(None, [], 'cp', pi_src, rho_path)

  # Check that file copied.
  expected_status.tweak('A/D/G/rho', status='R ', copied='+', wc_rev='-')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Now delete replaced file.
  svntest.actions.run_and_verify_svn(None, [], 'rm',
                                     '--force', rho_path)

  # Verify status after deletion.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/G/rho', status='D ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)


@Issue(2436)
def mv_unversioned_file(sbox):
  "move an unversioned file"
  # Issue #2436: Attempting to move an unversioned file would seg fault.
  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  unver_path_1 = sbox.ospath('unversioned1')
  dest_path_1 = sbox.ospath('dest')
  svntest.main.file_append(unver_path_1, "an unversioned file")

  unver_path_2 = sbox.ospath('A/unversioned2')
  dest_path_2 = sbox.ospath('A/dest_forced')
  svntest.main.file_append(unver_path_2, "another unversioned file")

  # Try to move an unversioned file.
  svntest.actions.run_and_verify_svn(None,
                                     ".*unversioned1' " +
                                       "(does not exist|is not under version control)",
                                     'mv', unver_path_1, dest_path_1)

  # Try to forcibly move an unversioned file.
  svntest.actions.run_and_verify_svn(None,
                                     ".*unversioned2.* " +
                                       "(does not exist|is not under version control)",
                                     'mv',
                                     unver_path_2, dest_path_2)

@Issue(2435)
def force_move(sbox):
  "'move' should not lose local mods"
  # Issue #2435: 'svn move' / 'svn mv' can lose local modifications.
  sbox.build()
  wc_dir = sbox.wc_dir
  file_name = "iota"
  file_path = os.path.join(wc_dir, file_name)

  # modify the content
  file_handle = open(file_path, "a")
  file_handle.write("Added contents\n")
  file_handle.close()
  expected_file_content = [ "This is the file 'iota'.\n",
                            "Added contents\n",
                          ]

  # check for the new content
  file_handle = open(file_path, "r")
  modified_file_content = file_handle.readlines()
  file_handle.close()
  if modified_file_content != expected_file_content:
    raise svntest.Failure("Test setup failed. Incorrect file contents.")

  # force move the file
  move_output = [ "A         dest\n",
                  "D         iota\n",
                ]
  was_cwd = os.getcwd()
  os.chdir(wc_dir)

  svntest.actions.run_and_verify_svn(move_output,
                                     [],
                                     'move',
                                     file_name, "dest")
  os.chdir(was_cwd)

  # check for the new content
  file_handle = open(sbox.ospath('dest'), "r")
  modified_file_content = file_handle.readlines()
  file_handle.close()
  # Error if we dont find the modified contents...
  if modified_file_content != expected_file_content:
    raise svntest.Failure("File modifications were lost on 'move'")

  # Commit the move and make sure the new content actually reaches
  # the repository.
  expected_output = svntest.wc.State(wc_dir, {
    'iota': Item(verb='Deleting'),
    'dest': Item(verb='Adding'),
  })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove("iota")
  expected_status.add({
    'dest': Item(status='  ', wc_rev='2'),
  })
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)
  svntest.actions.run_and_verify_svn(expected_file_content, [],
                                     'cat',
                                     sbox.repo_url + '/dest')


def copy_copied_file_and_dir(sbox):
  "copy a copied file and dir"
  # Improve support for copy and move
  # Allow copy of copied items without a commit between

  sbox.build()
  wc_dir = sbox.wc_dir

  rho_path = sbox.ospath('A/D/G/rho')
  rho_copy_path_1 = sbox.ospath('A/D/rho_copy_1')
  rho_copy_path_2 = sbox.ospath('A/B/F/rho_copy_2')

  # Copy A/D/G/rho to A/D/rho_copy_1
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     rho_path, rho_copy_path_1)

  # Copy the copied file: A/D/rho_copy_1 to A/B/F/rho_copy_2
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     rho_copy_path_1, rho_copy_path_2)

  E_path = sbox.ospath('A/B/E')
  E_path_copy_1 = sbox.ospath('A/B/F/E_copy_1')
  E_path_copy_2 = sbox.ospath('A/D/G/E_copy_2')

  # Copy A/B/E to A/B/F/E_copy_1
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     E_path, E_path_copy_1)

  # Copy the copied dir: A/B/F/E_copy_1 to A/D/G/E_copy_2
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     E_path_copy_1, E_path_copy_2)

  # Created expected output tree for 'svn ci':
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/rho_copy_1'       : Item(verb='Adding'),
    'A/B/F/rho_copy_2'     : Item(verb='Adding'),
    'A/B/F/E_copy_1/'      : Item(verb='Adding'),
    'A/D/G/E_copy_2/'      : Item(verb='Adding'),
    })

  # Create expected status tree
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/D/rho_copy_1'       : Item(status='  ', wc_rev=2),
    'A/B/F/rho_copy_2'     : Item(status='  ', wc_rev=2),
    'A/B/F/E_copy_1'       : Item(status='  ', wc_rev=2),
    'A/B/F/E_copy_1/alpha' : Item(status='  ', wc_rev=2),
    'A/B/F/E_copy_1/beta'  : Item(status='  ', wc_rev=2),
    'A/D/G/E_copy_2'       : Item(status='  ', wc_rev=2),
    'A/D/G/E_copy_2/alpha' : Item(status='  ', wc_rev=2),
    'A/D/G/E_copy_2/beta'  : Item(status='  ', wc_rev=2),
    })

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


def move_copied_file_and_dir(sbox):
  "move a copied file and dir"

  sbox.build()
  wc_dir = sbox.wc_dir

  rho_path = sbox.ospath('A/D/G/rho')
  rho_copy_path = sbox.ospath('A/D/rho_copy')
  rho_copy_move_path = sbox.ospath('A/B/F/rho_copy_moved')

  # Copy A/D/G/rho to A/D/rho_copy
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     rho_path, rho_copy_path)

  # Move the copied file: A/D/rho_copy to A/B/F/rho_copy_moved
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     rho_copy_path, rho_copy_move_path)

  E_path = sbox.ospath('A/B/E')
  E_path_copy = sbox.ospath('A/B/F/E_copy')
  E_path_copy_move = sbox.ospath('A/D/G/E_copy_moved')

  # Copy A/B/E to A/B/F/E_copy
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     E_path, E_path_copy)

  # Move the copied file: A/B/F/E_copy to A/D/G/E_copy_moved
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     E_path_copy, E_path_copy_move)

  # Created expected output tree for 'svn ci':
  # Since we are moving items that were only *scheduled* for addition
  # we expect only to additions when checking in, rather than a
  # deletion/addition pair.
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/F/rho_copy_moved' : Item(verb='Adding'),
    'A/D/G/E_copy_moved/'  : Item(verb='Adding'),
    })

  # Create expected status tree
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/B/F/rho_copy_moved'     : Item(status='  ', wc_rev=2),
    'A/D/G/E_copy_moved'       : Item(status='  ', wc_rev=2),
    'A/D/G/E_copy_moved/alpha' : Item(status='  ', wc_rev=2),
    'A/D/G/E_copy_moved/beta'  : Item(status='  ', wc_rev=2),
    })

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


def move_moved_file_and_dir(sbox):
  "move a moved file and dir"

  sbox.build()
  wc_dir = sbox.wc_dir

  rho_path = sbox.ospath('A/D/G/rho')
  rho_move_path = sbox.ospath('A/D/rho_moved')
  rho_move_moved_path = sbox.ospath('A/B/F/rho_move_moved')

  # Move A/D/G/rho to A/D/rho_moved
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     rho_path, rho_move_path)

  # Move the moved file: A/D/rho_moved to A/B/F/rho_move_moved
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     rho_move_path, rho_move_moved_path)

  E_path = sbox.ospath('A/B/E')
  E_path_moved = sbox.ospath('A/B/F/E_moved')
  E_path_move_moved = sbox.ospath('A/D/G/E_move_moved')

  # Copy A/B/E to A/B/F/E_moved
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     E_path, E_path_moved)

  # Move the moved file: A/B/F/E_moved to A/D/G/E_move_moved
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     E_path_moved, E_path_move_moved)

  # Created expected output tree for 'svn ci':
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/E'                : Item(verb='Deleting'),
    'A/D/G/E_move_moved/'  : Item(verb='Adding'),
    'A/D/G/rho'            : Item(verb='Deleting'),
    'A/B/F/rho_move_moved' : Item(verb='Adding'),
    })

  # Create expected status tree
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/D/G/E_move_moved'       : Item(status='  ', wc_rev=2),
    'A/D/G/E_move_moved/alpha' : Item(status='  ', wc_rev=2),
    'A/D/G/E_move_moved/beta'  : Item(status='  ', wc_rev=2),
    'A/B/F/rho_move_moved'     : Item(status='  ', wc_rev=2),
    })

  expected_status.remove('A/B/E',
                         'A/B/E/alpha',
                         'A/B/E/beta',
                         'A/D/G/rho')

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


def move_file_within_moved_dir(sbox):
  "move a file twice within a moved dir"

  sbox.build()
  wc_dir = sbox.wc_dir

  D_path = sbox.ospath('A/D')
  D_path_moved = sbox.ospath('A/B/F/D_moved')

  # Move A/B/D to A/B/F/D_moved
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     D_path, D_path_moved)

  chi_path = sbox.ospath('A/B/F/D_moved/H/chi')
  chi_moved_path = os.path.join(wc_dir, 'A', 'B', 'F', 'D_moved',
                                'H', 'chi_moved')
  chi_moved_again_path = os.path.join(wc_dir, 'A', 'B', 'F',
                                      'D_moved', 'H', 'chi_moved_again')

  # Move A/B/F/D_moved/H/chi to A/B/F/D_moved/H/chi_moved
  # then move that to A/B/F/D_moved/H/chi_moved_again
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     chi_path, chi_moved_path)
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     chi_moved_path,
                                     chi_moved_again_path)

  # Created expected output tree for 'svn ci':
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/F/D_moved/'                  : Item(verb='Adding'),
    'A/B/F/D_moved/H/chi'             : Item(verb='Deleting'),
    'A/B/F/D_moved/H/chi_moved_again' : Item(verb='Adding'),
    'A/D'                             : Item(verb='Deleting'),
    })

  # Create expected status tree
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/B/F/D_moved'                   : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/gamma'             : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G'                 : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G/pi'              : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G/rho'             : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G/tau'             : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/H'                 : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/H/omega'           : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/H/psi'             : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/H/chi_moved_again' : Item(status='  ', wc_rev=2),
    })

  expected_status.remove('A/D',
                         'A/D/gamma',
                         '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',
                         )

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


def move_file_out_of_moved_dir(sbox):
  "move a file out of a moved dir"

  sbox.build()
  wc_dir = sbox.wc_dir

  D_path = sbox.ospath('A/D')
  D_path_moved = sbox.ospath('A/B/F/D_moved')

  # Move A/B/D to A/B/F/D_moved
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     D_path, D_path_moved)

  chi_path = sbox.ospath('A/B/F/D_moved/H/chi')
  chi_moved_path = os.path.join(wc_dir, 'A', 'B', 'F', 'D_moved',
                                'H', 'chi_moved')
  chi_moved_again_path = sbox.ospath('A/C/chi_moved_again')

  # Move A/B/F/D_moved/H/chi to A/B/F/D_moved/H/chi_moved
  # then move that to A/C/chi_moved_again
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     chi_path, chi_moved_path)
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     chi_moved_path,
                                     chi_moved_again_path)

  # Created expected output tree for 'svn ci':
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/F/D_moved/'      : Item(verb='Adding'),
    'A/B/F/D_moved/H/chi' : Item(verb='Deleting'),
    'A/C/chi_moved_again' : Item(verb='Adding'),
    'A/D'                 : Item(verb='Deleting'),
    })

  # Create expected status tree
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/B/F/D_moved'         : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/gamma'   : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G'       : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G/pi'    : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G/rho'   : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G/tau'   : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/H'       : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/H/omega' : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/H/psi'   : Item(status='  ', wc_rev=2),
    'A/C/chi_moved_again'   : Item(status='  ', wc_rev=2),
    })

  expected_status.remove('A/D',
                         'A/D/gamma',
                         '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',
                         )

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


def move_dir_within_moved_dir(sbox):
  "move a dir twice within a moved dir"

  sbox.build()
  wc_dir = sbox.wc_dir

  D_path = sbox.ospath('A/D')
  D_path_moved = sbox.ospath('A/B/F/D_moved')

  # Move A/D to A/B/F/D_moved
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     D_path, D_path_moved)

  H_path = sbox.ospath('A/B/F/D_moved/H')
  H_moved_path = sbox.ospath('A/B/F/D_moved/H_moved')
  H_moved_again_path = os.path.join(wc_dir, 'A', 'B', 'F',
                                    'D_moved', 'H_moved_again')

  # Move A/B/F/D_moved/H to A/B/F/D_moved/H_moved
  # then move that to A/B/F/D_moved/H_moved_again
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     H_path, H_moved_path)
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     H_moved_path,
                                     H_moved_again_path)

  # Created expected output tree for 'svn ci':
  expected_output = svntest.wc.State(wc_dir, {
    'A/D'                         : Item(verb='Deleting'),
    'A/B/F/D_moved'               : Item(verb='Adding'),
    'A/B/F/D_moved/H'             : Item(verb='Deleting'),
    'A/B/F/D_moved/H_moved_again' : Item(verb='Adding'),
    })

  # Create expected status tree
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/B/F/D_moved'                     : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/gamma'               : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G'                   : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G/pi'                : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G/rho'               : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G/tau'               : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/H_moved_again'       : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/H_moved_again/omega' : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/H_moved_again/psi'   : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/H_moved_again/chi'   : Item(status='  ', wc_rev=2),
    })

  expected_status.remove('A/D',
                         'A/D/gamma',
                         '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',
                         )

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


def move_dir_out_of_moved_dir(sbox):
  "move a dir out of a moved dir"

  sbox.build()
  wc_dir = sbox.wc_dir

  D_path = sbox.ospath('A/D')
  D_path_moved = sbox.ospath('A/B/F/D_moved')

  # Move A/D to A/B/F/D_moved
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     D_path, D_path_moved)

  H_path = sbox.ospath('A/B/F/D_moved/H')
  H_moved_path = sbox.ospath('A/B/F/D_moved/H_moved')
  H_moved_again_path = sbox.ospath('A/C/H_moved_again')

  # Move A/B/F/D_moved/H to A/B/F/D_moved/H_moved
  # then move that to A/C/H_moved_again
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     H_path, H_moved_path)
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     H_moved_path,
                                     H_moved_again_path)

  # Created expected output tree for 'svn ci':
  expected_output = svntest.wc.State(wc_dir, {
    'A/D'               : Item(verb='Deleting'),
    'A/B/F/D_moved'     : Item(verb='Adding'),
    'A/B/F/D_moved/H'   : Item(verb='Deleting'),
    'A/C/H_moved_again' : Item(verb='Adding'),
    })

  # Create expected status tree
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/B/F/D_moved'           : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/gamma'     : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G'         : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G/pi'      : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G/rho'     : Item(status='  ', wc_rev=2),
    'A/B/F/D_moved/G/tau'     : Item(status='  ', wc_rev=2),
    'A/C/H_moved_again'       : Item(status='  ', wc_rev=2),
    'A/C/H_moved_again/omega' : Item(status='  ', wc_rev=2),
    'A/C/H_moved_again/psi'   : Item(status='  ', wc_rev=2),
    'A/C/H_moved_again/chi'   : Item(status='  ', wc_rev=2),
    })

  expected_status.remove('A/D',
                         'A/D/gamma',
                         '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',
                         )

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

# Includes regression testing for issue #3429 ("svn mv A B; svn mv B A"
# generates replace without history).
@Issue(3429)
def move_file_back_and_forth(sbox):
  "move a moved file back to original location"

  sbox.build()
  wc_dir = sbox.wc_dir

  rho_path = sbox.ospath('A/D/G/rho')
  rho_move_path = sbox.ospath('A/D/rho_moved')

  # Move A/D/G/rho away from and then back to its original path
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     rho_path, rho_move_path)
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     rho_move_path, rho_path)

  # Check expected status before commit
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Try to commit and find out that there is nothing to commit.
  expected_output = []
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)


# Includes regression testing for issue #3429 ("svn mv A B; svn mv B A"
# generates replace without history).
@Issue(3429)
def move_dir_back_and_forth(sbox):
  "move a moved dir back to original location"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  D_path = sbox.ospath('A/D')
  D_move_path = sbox.ospath('D_moved')

  # Move A/D to D_moved
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     D_path, D_move_path)

  # Move the moved dir: D_moved back to its starting
  # location at A/D.

  svntest.actions.run_and_verify_svn(None, [], 'mv', D_move_path, D_path)

  # Verify that the status indicates a replace with history
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

def copy_move_added_paths(sbox):
  "copy and move added paths without commits"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Create a new file and schedule it for addition
  upsilon_path = sbox.ospath('A/D/upsilon')
  svntest.main.file_write(upsilon_path, "This is the file 'upsilon'\n")
  svntest.actions.run_and_verify_svn(None, [], 'add', upsilon_path)

  # Create a dir with children and schedule it for addition
  I_path = sbox.ospath('A/D/I')
  J_path = os.path.join(I_path, 'J')
  eta_path = os.path.join(I_path, 'eta')
  theta_path = os.path.join(I_path, 'theta')
  kappa_path = os.path.join(J_path, 'kappa')
  os.mkdir(I_path)
  os.mkdir(J_path)
  svntest.main.file_write(eta_path, "This is the file 'eta'\n")
  svntest.main.file_write(theta_path, "This is the file 'theta'\n")
  svntest.main.file_write(kappa_path, "This is the file 'kappa'\n")
  svntest.actions.run_and_verify_svn(None, [], 'add', I_path)

  # Create another dir and schedule it for addition
  K_path = sbox.ospath('K')
  os.mkdir(K_path)
  svntest.actions.run_and_verify_svn(None, [], 'add', K_path)

  # Verify all the adds took place correctly.
  expected_status_after_adds = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status_after_adds.add({
    'A/D/I'         : Item(status='A ', wc_rev='0'),
    'A/D/I/eta'     : Item(status='A ', wc_rev='0'),
    'A/D/I/J'       : Item(status='A ', wc_rev='0'),
    'A/D/I/J/kappa' : Item(status='A ', wc_rev='0'),
    'A/D/I/theta'   : Item(status='A ', wc_rev='0'),
    'A/D/upsilon'   : Item(status='A ', wc_rev='0'),
    'K'             : Item(status='A ', wc_rev='0'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_status_after_adds)

  # Scatter some unversioned paths within the added dir I.
  unversioned_path_1 = os.path.join(I_path, 'unversioned1')
  unversioned_path_2 = os.path.join(J_path, 'unversioned2')
  L_path = os.path.join(I_path, "L_UNVERSIONED")
  unversioned_path_3 = os.path.join(L_path, 'unversioned3')
  svntest.main.file_write(unversioned_path_1, "An unversioned file\n")
  svntest.main.file_write(unversioned_path_2, "An unversioned file\n")
  os.mkdir(L_path)
  svntest.main.file_write(unversioned_path_3, "An unversioned file\n")

  # Copy added dir A/D/I to added dir K/I
  I_copy_path = os.path.join(K_path, 'I')
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     I_path, I_copy_path)

  # Copy added file A/D/upsilon into added dir K
  upsilon_copy_path = os.path.join(K_path, 'upsilon')
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     upsilon_path, upsilon_copy_path)

  # Move added file A/D/upsilon to upsilon,
  # then move it again to A/upsilon
  upsilon_move_path = sbox.ospath('upsilon')
  upsilon_move_path_2 = sbox.ospath('A/upsilon')
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     upsilon_path, upsilon_move_path)
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     upsilon_move_path, upsilon_move_path_2)

  # Move added dir A/D/I to A/B/I,
  # then move it again to A/D/H/I
  I_move_path = sbox.ospath('A/B/I')
  I_move_path_2 = sbox.ospath('A/D/H/I')
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     I_path, I_move_path)
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     I_move_path, I_move_path_2)

  # Created expected output tree for 'svn ci'
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/H/I'         : Item(verb='Adding'),
    'A/D/H/I/J'       : Item(verb='Adding'),
    'A/D/H/I/J/kappa' : Item(verb='Adding'),
    'A/D/H/I/eta'     : Item(verb='Adding'),
    'A/D/H/I/theta'   : Item(verb='Adding'),
    'A/upsilon'       : Item(verb='Adding'),
    'K'               : Item(verb='Adding'),
    'K/I'             : Item(verb='Adding'),
    'K/I/J'           : Item(verb='Adding'),
    'K/I/J/kappa'     : Item(verb='Adding'),
    'K/I/eta'         : Item(verb='Adding'),
    'K/I/theta'       : Item(verb='Adding'),
    'K/upsilon'       : Item(verb='Adding'),
    })

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

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

  # Run_and_verify_commit() doesn't handle status of unversioned paths
  # so manually confirm unversioned paths got copied and moved too.
  unversioned_paths = [
    sbox.ospath('A/D/H/I/unversioned1'),
    sbox.ospath('A/D/H/I/L_UNVERSIONED'),
    os.path.join(wc_dir, 'A', 'D', 'H', 'I', 'L_UNVERSIONED',
                 'unversioned3'),
    sbox.ospath('A/D/H/I/J/unversioned2'),
    sbox.ospath('K/I/unversioned1'),
    sbox.ospath('K/I/L_UNVERSIONED'),
    sbox.ospath('K/I/L_UNVERSIONED/unversioned3'),
    sbox.ospath('K/I/J/unversioned2')]
  for path in unversioned_paths:
    if not os.path.exists(path):
      raise svntest.Failure("Unversioned path '%s' not found." % path)

def copy_added_paths_with_props(sbox):
  "copy added uncommitted paths with props"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Create a new file, schedule it for addition and set properties
  upsilon_path = sbox.ospath('A/D/upsilon')
  svntest.main.file_write(upsilon_path, "This is the file 'upsilon'\n")
  svntest.actions.run_and_verify_svn(None, [], 'add', upsilon_path)
  svntest.actions.run_and_verify_svn(None, [], 'propset',
                                     'foo', 'bar', upsilon_path)

  # Create a dir and schedule it for addition and set properties
  I_path = sbox.ospath('A/D/I')
  os.mkdir(I_path)
  svntest.actions.run_and_verify_svn(None, [], 'add', I_path)
  svntest.actions.run_and_verify_svn(None, [], 'propset',
                                     'foo', 'bar', I_path)

  # Verify all the adds took place correctly.
  expected_status_after_adds = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status_after_adds.add({
    'A/D/upsilon'   : Item(status='A ', wc_rev='0'),
    'A/D/I'         : Item(status='A ', wc_rev='0'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_status_after_adds)

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/D/upsilon' : Item(props={'foo' : 'bar'},
                         contents="This is the file 'upsilon'\n"),
    'A/D/I'       : Item(props={'foo' : 'bar'}),
    })

  svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True)

  # Copy added dir I to dir A/C
  I_copy_path = sbox.ospath('A/C/I')
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     I_path, I_copy_path)

  # Copy added file A/upsilon into dir A/C
  upsilon_copy_path = sbox.ospath('A/C/upsilon')
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     upsilon_path, upsilon_copy_path)

  # Created expected output tree for 'svn ci'
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/upsilon'     : Item(verb='Adding'),
    'A/D/I'           : Item(verb='Adding'),
    'A/C/upsilon'     : 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/D/upsilon'     : Item(status='  ', wc_rev=2),
    'A/D/I'           : Item(status='  ', wc_rev=2),
    'A/C/upsilon'     : Item(status='  ', wc_rev=2),
    'A/C/I'           : Item(status='  ', wc_rev=2),
    })

  # Tweak expected disk tree
  expected_disk.add({
    'A/C/upsilon' : Item(props={ 'foo' : 'bar'},
                         contents="This is the file 'upsilon'\n"),
    'A/C/I'       : Item(props={ 'foo' : 'bar'}),
    })

  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)
  svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True)

def copy_added_paths_to_URL(sbox):
  "copy added path to URL"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Create a new file and schedule it for addition
  upsilon_path = sbox.ospath('A/D/upsilon')
  svntest.main.file_write(upsilon_path, "This is the file 'upsilon'\n")
  svntest.actions.run_and_verify_svn(None, [], 'add', upsilon_path)

  # Create a dir with children and schedule it for addition
  I_path = sbox.ospath('A/D/I')
  J_path = os.path.join(I_path, 'J')
  eta_path = os.path.join(I_path, 'eta')
  theta_path = os.path.join(I_path, 'theta')
  kappa_path = os.path.join(J_path, 'kappa')
  os.mkdir(I_path)
  os.mkdir(J_path)
  svntest.main.file_write(eta_path, "This is the file 'eta'\n")
  svntest.main.file_write(theta_path, "This is the file 'theta'\n")
  svntest.main.file_write(kappa_path, "This is the file 'kappa'\n")
  svntest.actions.run_and_verify_svn(None, [], 'add', I_path)

  # Verify all the adds took place correctly.
  expected_status_after_adds = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status_after_adds.add({
    'A/D/I'         : Item(status='A ', wc_rev='0'),
    'A/D/I/eta'     : Item(status='A ', wc_rev='0'),
    'A/D/I/J'       : Item(status='A ', wc_rev='0'),
    'A/D/I/J/kappa' : Item(status='A ', wc_rev='0'),
    'A/D/I/theta'   : Item(status='A ', wc_rev='0'),
    'A/D/upsilon'   : Item(status='A ', wc_rev='0'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_status_after_adds)

  # Scatter some unversioned paths within the added dir I.
  # These don't get copied in a WC->URL copy obviously.
  unversioned_path_1 = os.path.join(I_path, 'unversioned1')
  unversioned_path_2 = os.path.join(J_path, 'unversioned2')
  L_path = os.path.join(I_path, "L_UNVERSIONED")
  unversioned_path_3 = os.path.join(L_path, 'unversioned3')
  svntest.main.file_write(unversioned_path_1, "An unversioned file\n")
  svntest.main.file_write(unversioned_path_2, "An unversioned file\n")
  os.mkdir(L_path)
  svntest.main.file_write(unversioned_path_3, "An unversioned file\n")

  # Copy added file A/D/upsilon to URL://A/C/upsilon
  upsilon_copy_URL = sbox.repo_url + '/A/C/upsilon'
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', '-m', '',
                                     upsilon_path, upsilon_copy_URL)

  # Validate the mergeinfo of the copy destination (we expect none).
  svntest.actions.run_and_verify_svn([], '.*W200017: Property.*not found',
                                     'propget',
                                     SVN_PROP_MERGEINFO, upsilon_copy_URL)

  # Copy added dir A/D/I to URL://A/D/G/I
  I_copy_URL = sbox.repo_url + '/A/D/G/I'
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', '-m', '',
                                     I_path, I_copy_URL)

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

  # Create expected status tree
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/D/I'         : Item(status='  ', wc_rev=4),
    'A/D/I/J'       : Item(status='  ', wc_rev=4),
    'A/D/I/J/kappa' : Item(status='  ', wc_rev=4),
    'A/D/I/eta'     : Item(status='  ', wc_rev=4),
    'A/D/I/theta'   : Item(status='  ', wc_rev=4),
    'A/D/upsilon'   : Item(status='  ', wc_rev=4),
    })

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

  # Created expected output for update
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/G/I'         : Item(status='A '),
    'A/D/G/I/theta'   : Item(status='A '),
    'A/D/G/I/J'       : Item(status='A '),
    'A/D/G/I/J/kappa' : Item(status='A '),
    'A/D/G/I/eta'     : Item(status='A '),
    'A/C/upsilon'     : Item(status='A '),
    })

  # Created expected disk for update
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/D/G/I'                          : Item(),
    'A/D/G/I/theta'                    : Item("This is the file 'theta'\n"),
    'A/D/G/I/J'                        : Item(),
    'A/D/G/I/J/kappa'                  : Item("This is the file 'kappa'\n"),
    'A/D/G/I/eta'                      : Item("This is the file 'eta'\n"),
    'A/C/upsilon'                      : Item("This is the file 'upsilon'\n"),
    'A/D/I'                            : Item(),
    'A/D/I/J'                          : Item(),
    'A/D/I/J/kappa'                    : Item("This is the file 'kappa'\n"),
    'A/D/I/eta'                        : Item("This is the file 'eta'\n"),
    'A/D/I/theta'                      : Item("This is the file 'theta'\n"),
    'A/D/upsilon'                      : Item("This is the file 'upsilon'\n"),
    'A/D/I/L_UNVERSIONED/unversioned3' : Item("An unversioned file\n"),
    'A/D/I/L_UNVERSIONED'              : Item(),
    'A/D/I/unversioned1'               : Item("An unversioned file\n"),
    'A/D/I/J/unversioned2'             : Item("An unversioned file\n"),
    })

  # Some more changes to the expected_status to reflect post update WC
  expected_status.tweak(wc_rev=4)
  expected_status.add({
    'A/C'             : Item(status='  ', wc_rev=4),
    'A/C/upsilon'     : Item(status='  ', wc_rev=4),
    'A/D/G'           : Item(status='  ', wc_rev=4),
    'A/D/G/I'         : Item(status='  ', wc_rev=4),
    'A/D/G/I/theta'   : Item(status='  ', wc_rev=4),
    'A/D/G/I/J'       : Item(status='  ', wc_rev=4),
    'A/D/G/I/J/kappa' : Item(status='  ', wc_rev=4),
    'A/D/G/I/eta'     : Item(status='  ', wc_rev=4),
    })

  # Update WC, the WC->URL copies above should be added
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status)


# Issue #1869.
@Issue(1869)
def move_to_relative_paths(sbox):
  "move file using relative dst path names"

  sbox.build()
  wc_dir = sbox.wc_dir
  E_path = sbox.ospath('A/B/E')
  rel_path = os.path.join('..', '..', '..')

  current_dir = os.getcwd()
  os.chdir(E_path)
  svntest.main.run_svn(None, 'mv', 'beta', rel_path)
  os.chdir(current_dir)

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'beta'        : Item(status='A ', copied='+', wc_rev='-',
                         moved_from='A/B/E/beta'),
    'A/B/E/beta'  : Item(status='D ', wc_rev='1', moved_to='beta')
  })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)


#----------------------------------------------------------------------
def move_from_relative_paths(sbox):
  "move file using relative src path names"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir
  F_path = sbox.ospath('A/B/F')
  beta_rel_path = os.path.join('..', 'E', 'beta')

  current_dir = os.getcwd()
  os.chdir(F_path)
  svntest.main.run_svn(None, 'mv', beta_rel_path, '.')
  os.chdir(current_dir)

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/B/F/beta'  : Item(status='A ', copied='+', wc_rev='-',
                         moved_from='A/B/E/beta'),
    'A/B/E/beta'  : Item(status='D ', wc_rev='1', moved_to='A/B/F/beta')
  })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)


#----------------------------------------------------------------------
def copy_to_relative_paths(sbox):
  "copy file using relative dst path names"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir
  E_path = sbox.ospath('A/B/E')
  rel_path = os.path.join('..', '..', '..')

  current_dir = os.getcwd()
  os.chdir(E_path)
  svntest.main.run_svn(None, 'cp', 'beta', rel_path)
  os.chdir(current_dir)

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'beta'        : Item(status='A ', copied='+', wc_rev='-'),
  })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)


#----------------------------------------------------------------------
def copy_from_relative_paths(sbox):
  "copy file using relative src path names"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir
  F_path = sbox.ospath('A/B/F')
  beta_rel_path = os.path.join('..', 'E', 'beta')

  current_dir = os.getcwd()
  os.chdir(F_path)
  svntest.main.run_svn(None, 'cp', beta_rel_path, '.')
  os.chdir(current_dir)

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/B/F/beta'  : Item(status='A ', copied='+', wc_rev='-'),
  })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)


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

# Test moving multiple files within a wc.

def move_multiple_wc(sbox):
  "svn mv multiple files to a common directory"

  sbox.build()
  wc_dir = sbox.wc_dir

  chi_path = sbox.ospath('A/D/H/chi')
  psi_path = sbox.ospath('A/D/H/psi')
  omega_path = sbox.ospath('A/D/H/omega')
  E_path = sbox.ospath('A/B/E')
  C_path = sbox.ospath('A/C')

  # Move chi, psi, omega and E to A/C
  svntest.actions.run_and_verify_svn(None, [], 'mv', chi_path, psi_path,
                                     omega_path, E_path, C_path)

  # Create expected output
  expected_output = svntest.wc.State(wc_dir, {
    'A/C/chi'     : Item(verb='Adding'),
    'A/C/psi'     : Item(verb='Adding'),
    'A/C/omega'   : Item(verb='Adding'),
    'A/C/E'       : Item(verb='Adding'),
    'A/D/H/chi'   : Item(verb='Deleting'),
    'A/D/H/psi'   : Item(verb='Deleting'),
    'A/D/H/omega' : Item(verb='Deleting'),
    'A/B/E'       : Item(verb='Deleting'),
    })

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

  # Add the moved files
  expected_status.add({
    'A/C/chi'     : Item(status='  ', wc_rev=2),
    'A/C/psi'     : Item(status='  ', wc_rev=2),
    'A/C/omega'   : Item(status='  ', wc_rev=2),
    'A/C/E'       : Item(status='  ', wc_rev=2),
    'A/C/E/alpha' : Item(status='  ', wc_rev=2),
    'A/C/E/beta'  : Item(status='  ', wc_rev=2),
    })

  # Removed the moved files
  expected_status.remove('A/D/H/chi', 'A/D/H/psi', 'A/D/H/omega', 'A/B/E/alpha',
                         'A/B/E/beta', 'A/B/E')

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

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

# Test copying multiple files within a wc.

def copy_multiple_wc(sbox):
  "svn cp multiple files to a common directory"

  sbox.build()
  wc_dir = sbox.wc_dir

  chi_path = sbox.ospath('A/D/H/chi')
  psi_path = sbox.ospath('A/D/H/psi')
  omega_path = sbox.ospath('A/D/H/omega')
  E_path = sbox.ospath('A/B/E')
  C_path = sbox.ospath('A/C')

  # Copy chi, psi, omega and E to A/C
  svntest.actions.run_and_verify_svn(None, [], 'cp', chi_path, psi_path,
                                     omega_path, E_path, C_path)

  # Create expected output
  expected_output = svntest.wc.State(wc_dir, {
    'A/C/chi'     : Item(verb='Adding'),
    'A/C/psi'     : Item(verb='Adding'),
    'A/C/omega'   : Item(verb='Adding'),
    'A/C/E'       : Item(verb='Adding'),
    })

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

  # Add the moved files
  expected_status.add({
    'A/C/chi'     : Item(status='  ', wc_rev=2),
    'A/C/psi'     : Item(status='  ', wc_rev=2),
    'A/C/omega'   : Item(status='  ', wc_rev=2),
    'A/C/E'       : Item(status='  ', wc_rev=2),
    'A/C/E/alpha' : Item(status='  ', wc_rev=2),
    'A/C/E/beta'  : Item(status='  ', wc_rev=2),
    })

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

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

# Test moving multiple files within a repo.

def move_multiple_repo(sbox):
  "move multiple files within a repo"

  sbox.build()
  wc_dir = sbox.wc_dir

  chi_url = sbox.repo_url + '/A/D/H/chi'
  psi_url = sbox.repo_url + '/A/D/H/psi'
  omega_url = sbox.repo_url + '/A/D/H/omega'
  E_url = sbox.repo_url + '/A/B/E'
  C_url = sbox.repo_url + '/A/C'

  # Move three files and a directory in the repo to a different location
  # in the repo
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     chi_url, psi_url, omega_url, E_url, C_url,
                                     '-m', 'logmsg')

  # Update to HEAD, and check to see if the files really moved in the repo

  expected_output = svntest.wc.State(wc_dir, {
    'A/C/chi'     : Item(status='A '),
    'A/C/psi'     : Item(status='A '),
    'A/C/omega'   : Item(status='A '),
    'A/C/E'       : Item(status='A '),
    'A/C/E/alpha' : Item(status='A '),
    'A/C/E/beta'  : Item(status='A '),
    'A/D/H/chi'   : Item(status='D '),
    'A/D/H/psi'   : Item(status='D '),
    'A/D/H/omega' : Item(status='D '),
    'A/B/E'       : Item(status='D '),
    })

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/D/H/chi', 'A/D/H/psi', 'A/D/H/omega', 'A/B/E/alpha',
                       'A/B/E/beta', 'A/B/E')
  expected_disk.add({
    'A/C/chi'     : Item(contents="This is the file 'chi'.\n"),
    'A/C/psi'     : Item(contents="This is the file 'psi'.\n"),
    'A/C/omega'   : Item(contents="This is the file 'omega'.\n"),
    'A/C/E'       : Item(),
    'A/C/E/alpha' : Item(contents="This is the file 'alpha'.\n"),
    'A/C/E/beta'  : Item(contents="This is the file 'beta'.\n"),
    })

  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.remove('A/D/H/chi', 'A/D/H/psi', 'A/D/H/omega', 'A/B/E/alpha',
                         'A/B/E/beta', 'A/B/E')
  expected_status.add({
    'A/C/chi'     : Item(status='  ', wc_rev=2),
    'A/C/psi'     : Item(status='  ', wc_rev=2),
    'A/C/omega'   : Item(status='  ', wc_rev=2),
    'A/C/E'       : Item(status='  ', wc_rev=2),
    'A/C/E/alpha' : Item(status='  ', wc_rev=2),
    'A/C/E/beta'  : Item(status='  ', wc_rev=2),
    })

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

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

# Test copying multiple files within a repo.

def copy_multiple_repo(sbox):
  "copy multiple files within a repo"

  sbox.build()
  wc_dir = sbox.wc_dir

  chi_url = sbox.repo_url + '/A/D/H/chi'
  psi_url = sbox.repo_url + '/A/D/H/psi'
  omega_url = sbox.repo_url + '/A/D/H/omega'
  E_url = sbox.repo_url + '/A/B/E'
  C_url = sbox.repo_url + '/A/C'

  # Copy three files and a directory in the repo to a different location
  # in the repo
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     chi_url, psi_url, omega_url, E_url, C_url,
                                     '-m', 'logmsg')

  # Update to HEAD, and check to see if the files really moved in the repo

  expected_output = svntest.wc.State(wc_dir, {
    'A/C/chi'     : Item(status='A '),
    'A/C/psi'     : Item(status='A '),
    'A/C/omega'   : Item(status='A '),
    'A/C/E'       : Item(status='A '),
    'A/C/E/alpha' : Item(status='A '),
    'A/C/E/beta'  : Item(status='A '),
    })

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/C/chi'     : Item(contents="This is the file 'chi'.\n"),
    'A/C/psi'     : Item(contents="This is the file 'psi'.\n"),
    'A/C/omega'   : Item(contents="This is the file 'omega'.\n"),
    'A/C/E'       : Item(),
    'A/C/E/alpha' : Item(contents="This is the file 'alpha'.\n"),
    'A/C/E/beta'  : Item(contents="This is the file 'beta'.\n"),
    })

  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'A/C/chi'     : Item(status='  ', wc_rev=2),
    'A/C/psi'     : Item(status='  ', wc_rev=2),
    'A/C/omega'   : Item(status='  ', wc_rev=2),
    'A/C/E'       : Item(status='  ', wc_rev=2),
    'A/C/E/alpha' : Item(status='  ', wc_rev=2),
    'A/C/E/beta'  : Item(status='  ', wc_rev=2),
    })

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

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

# Test moving copying multiple files from a repo to a wc
@Issue(2955)
def copy_multiple_repo_wc(sbox):
  "copy multiple files from a repo to a wc"

  sbox.build()
  wc_dir = sbox.wc_dir

  chi_url = sbox.repo_url + '/A/D/H/chi'
  psi_url = sbox.repo_url + '/A/D/H/psi'
  omega_with_space_url = sbox.repo_url + '/A/D/H/omega 2'
  E_url = sbox.repo_url + '/A/B/E'
  C_path = sbox.ospath('A/C')

  # We need this in order to check that we don't end up with URI-encoded
  # paths in the WC (issue #2955)
  svntest.actions.run_and_verify_svn(None, [], 'mv', '-m', 'log_msg',
                                     sbox.repo_url + '/A/D/H/omega',
                                     omega_with_space_url)

  # Perform the copy and check the output
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     chi_url, psi_url, omega_with_space_url,
                                     E_url, C_path)

  # Commit the changes, and verify the content actually got copied
  expected_output = svntest.wc.State(wc_dir, {
    'A/C/chi'     : Item(verb='Adding'),
    'A/C/psi'     : Item(verb='Adding'),
    'A/C/omega 2' : Item(verb='Adding'),
    'A/C/E'       : Item(verb='Adding'),
    })

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/C/chi'     : Item(status='  ', wc_rev=3),
    'A/C/psi'     : Item(status='  ', wc_rev=3),
    'A/C/omega 2' : Item(status='  ', wc_rev=3),
    'A/C/E'       : Item(status='  ', wc_rev=3),
    'A/C/E/alpha' : Item(status='  ', wc_rev=3),
    'A/C/E/beta'  : Item(status='  ', wc_rev=3),
    })

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

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

# Test moving copying multiple files from a wc to a repo

def copy_multiple_wc_repo(sbox):
  "copy multiple files from a wc to a repo"

  sbox.build()
  wc_dir = sbox.wc_dir

  chi_path = sbox.ospath('A/D/H/chi')
  psi_path = sbox.ospath('A/D/H/psi')
  omega_path = sbox.ospath('A/D/H/omega')
  E_path = sbox.ospath('A/B/E')
  C_url = sbox.repo_url + '/A/C'

  # Perform the copy and check the output
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     chi_path, psi_path, omega_path, E_path,
                                     C_url, '-m', 'logmsg')

  # Update to HEAD, and check to see if the files really got copied in the repo

  expected_output = svntest.wc.State(wc_dir, {
    'A/C/chi'     : Item(status='A '),
    'A/C/psi'     : Item(status='A '),
    'A/C/omega'   : Item(status='A '),
    'A/C/E'       : Item(status='A '),
    'A/C/E/alpha' : Item(status='A '),
    'A/C/E/beta'  : Item(status='A '),
    })

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/C/chi': Item(contents="This is the file 'chi'.\n"),
    'A/C/psi': Item(contents="This is the file 'psi'.\n"),
    'A/C/omega': Item(contents="This is the file 'omega'.\n"),
    'A/C/E'       : Item(),
    'A/C/E/alpha' : Item(contents="This is the file 'alpha'.\n"),
    'A/C/E/beta'  : Item(contents="This is the file 'beta'.\n"),
    })

  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'A/C/chi'     : Item(status='  ', wc_rev=2),
    'A/C/psi'     : Item(status='  ', wc_rev=2),
    'A/C/omega'   : Item(status='  ', wc_rev=2),
    'A/C/E'       : Item(status='  ', wc_rev=2),
    'A/C/E/alpha' : Item(status='  ', wc_rev=2),
    'A/C/E/beta'  : Item(status='  ', wc_rev=2),
    })

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

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

# Test copying local files using peg revision syntax
# (Issue 2546)
@Issue(2546)
def copy_peg_rev_local_files(sbox):
  "copy local files using peg rev syntax"

  sbox.build()
  wc_dir = sbox.wc_dir

  psi_path = sbox.ospath('A/D/H/psi')
  new_iota_path = sbox.ospath('new_iota')
  iota_path = sbox.ospath('iota')
  sigma_path = sbox.ospath('sigma')

  psi_text = "This is the file 'psi'.\n"
  iota_text = "This is the file 'iota'.\n"

  # Play a shell game with some WC files, then commit the changes back
  # to the repository (making r2).
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     psi_path, new_iota_path)
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     iota_path, psi_path)
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     new_iota_path, iota_path)

  svntest.actions.run_and_verify_svn(None, [],
                                     'ci',
                                     '-m', 'rev 2',
                                     wc_dir)

  # Copy using a peg rev (remember, the object at iota_path at HEAD
  # was at psi_path back at r1).
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp',
                                     iota_path + '@HEAD', '-r', '1',
                                     sigma_path)

  # Commit and verify disk contents
  svntest.actions.run_and_verify_svn(None, [],
                                     'ci', wc_dir,
                                     '-m', 'rev 3')

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/D/H/psi', contents=iota_text)
  expected_disk.add({
    'iota'      : Item(contents=psi_text),
    'A/D/H/psi' : Item(contents=iota_text),
    'sigma'     : Item(contents=psi_text, props={}),
    })

  svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True)


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

# Test copying local directories using peg revision syntax
# (Issue 2546)
@Issue(2546)
def copy_peg_rev_local_dirs(sbox):
  "copy local dirs using peg rev syntax"

  sbox.build()
  wc_dir = sbox.wc_dir

  E_path = sbox.ospath('A/B/E')
  G_path = sbox.ospath('A/D/G')
  I_path = sbox.ospath('A/D/I')
  J_path = sbox.ospath('A/J')
  alpha_path = os.path.join(E_path, 'alpha')

  # Make some changes to the repository
  svntest.actions.run_and_verify_svn(None, [], 'rm',
                                     alpha_path)
  svntest.actions.run_and_verify_svn(None, [],
                                     'ci',
                                     '-m', 'rev 2',
                                     wc_dir)
  svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir)

  svntest.actions.run_and_verify_svn(None, [],
                                     'mv',
                                     E_path, I_path)
  svntest.actions.run_and_verify_svn(None, [],
                                     'ci',
                                     '-m', 'rev 3',
                                     wc_dir)

  svntest.actions.run_and_verify_svn(None, [],
                                     'mv',
                                     G_path, E_path)
  svntest.actions.run_and_verify_svn(None, [],
                                     'ci',
                                     '-m', 'rev 4',
                                     wc_dir)

  svntest.actions.run_and_verify_svn(None, [],
                                     'mv',
                                     I_path, G_path)
  svntest.actions.run_and_verify_svn(None, [],
                                     'ci',
                                     '-m', 'rev 5',
                                     wc_dir)

  # Copy using a peg rev
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp',
                                     G_path + '@HEAD', '-r', '1',
                                     J_path)

  # Commit and verify disk contents
  svntest.actions.run_and_verify_svn(None, [],
                                     'ci', wc_dir,
                                     '-m', 'rev 6')

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/B/E/beta')
  expected_disk.remove('A/B/E/alpha')
  expected_disk.remove('A/D/G/pi')
  expected_disk.remove('A/D/G/rho')
  expected_disk.remove('A/D/G/tau')
  expected_disk.add({
    'A/B/E'       : Item(),
    'A/B/E/pi'    : Item(contents="This is the file 'pi'.\n"),
    'A/B/E/rho'   : Item(contents="This is the file 'rho'.\n"),
    'A/B/E/tau'   : Item(contents="This is the file 'tau'.\n"),
    'A/D/G'       : Item(),
    'A/D/G/beta'  : Item(contents="This is the file 'beta'.\n"),
    'A/J'         : Item(),
    'A/J/alpha'   : Item(contents="This is the file 'alpha'.\n"),
    'A/J/beta'  : Item(contents="This is the file 'beta'.\n"),
    })

  svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True)


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

# Test copying urls using peg revision syntax
# (Issue 2546)
@Issues(2546,3651)
def copy_peg_rev_url(sbox):
  "copy urls using peg rev syntax"

  sbox.build()
  wc_dir = sbox.wc_dir

  psi_path = sbox.ospath('A/D/H/psi')
  new_iota_path = sbox.ospath('new_iota')
  iota_path = sbox.ospath('iota')
  iota_url = sbox.repo_url + '/iota'
  sigma_url = sbox.repo_url + '/sigma'

  psi_text = "This is the file 'psi'.\n"
  iota_text = "This is the file 'iota'.\n"

  # Make some changes to the repository
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     psi_path, new_iota_path)
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     iota_path, psi_path)
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     new_iota_path, iota_path)

  svntest.actions.run_and_verify_svn(None, [],
                                     'ci',
                                     '-m', 'rev 2',
                                     wc_dir)

  # Copy using a peg rev
  # Add an empty peg specifier ('@') to sigma_url when copying, to test for
  # issue #3651 "svn copy does not eat peg revision within copy target path".
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp',
                                     iota_url + '@HEAD', '-r', '1',
                                     sigma_url + '@', '-m', 'rev 3')

  # Validate the copy destination's mergeinfo (we expect none).
  svntest.actions.run_and_verify_svn([], '.*W200017: Property.*not found',
                                     'propget', SVN_PROP_MERGEINFO, sigma_url)

  # Update to HEAD and verify disk contents
  expected_output = svntest.wc.State(wc_dir, {
    'sigma' : Item(status='A '),
    })

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('iota', contents=psi_text)
  expected_disk.tweak('A/D/H/psi', contents=iota_text)
  expected_disk.add({
    'sigma' : Item(contents=psi_text),
    })

  expected_status = svntest.actions.get_virginal_state(wc_dir, 3)
  expected_status.add({
    'sigma' : Item(status='  ', wc_rev=3)
    })

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

# Test copying an older revision of a wc directory in the wc.
def old_dir_wc_to_wc(sbox):
  "copy old revision of wc dir to new dir"

  sbox.build()
  wc_dir = sbox.wc_dir

  E = sbox.ospath('A/B/E')
  E2 = sbox.ospath('E2')
  E_url = sbox.repo_url + '/A/B/E'
  alpha_url = E_url + '/alpha'

  # delete E/alpha in r2
  svntest.actions.run_and_verify_svn(None, [],
                                     'rm', '-m', '', alpha_url)

  # delete E in r3
  svntest.actions.run_and_verify_svn(None, [],
                                     'rm', '-m', '', E_url)

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

  # Create expected output tree.
  expected_output = svntest.wc.State(wc_dir, {
    'E2'      : Item(verb='Adding'),
    })

  # Created expected status tree.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'E2' : Item(status='  ', wc_rev=4),
    'E2/alpha'  : Item(status='  ', wc_rev=4),
    'E2/beta'  : Item(status='  ', wc_rev=4),
    })
  # Commit the one file.
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)


#----------------------------------------------------------------------
# Test copying and creating parents in the wc

def copy_make_parents_wc_wc(sbox):
  "svn cp --parents WC_PATH WC_PATH"

  sbox.build()
  wc_dir = sbox.wc_dir

  iota_path = sbox.ospath('iota')
  new_iota_path = sbox.ospath('X/Y/Z/iota')

  # Copy iota
  svntest.actions.run_and_verify_svn(None, [], 'cp', '--parents',
                                     iota_path, new_iota_path)

  # Create expected output
  expected_output = svntest.wc.State(wc_dir, {
    'X'           : Item(verb='Adding'),
    'X/Y'         : Item(verb='Adding'),
    'X/Y/Z'       : Item(verb='Adding'),
    'X/Y/Z/iota'  : Item(verb='Adding'),
    })

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

  # Add the moved files
  expected_status.add({
    'X'           : Item(status='  ', wc_rev=2),
    'X/Y'         : Item(status='  ', wc_rev=2),
    'X/Y/Z'       : Item(status='  ', wc_rev=2),
    'X/Y/Z/iota'  : Item(status='  ', wc_rev=2),
    })

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


#----------------------------------------------------------------------
# Test copying and creating parents in the wc with dst directory being
# precreated and unversioned

def copy_make_parents_wc_wc_existing_unversioned_dst(sbox):
  "svn cp --parents WC_PATH WC_PATH (ex. unver. dst)"

  sbox.build()
  wc_dir = sbox.wc_dir

  iota_path = sbox.ospath('iota')
  new_iota_path = sbox.ospath('X/Y/Z/iota')
  os.makedirs(os.path.dirname(new_iota_path))

  # Copy iota
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', '--parents',
                                     iota_path, new_iota_path)

  # Create expected output
  expected_output = svntest.wc.State(wc_dir, {
    'X'          : Item(verb='Adding'),
    'X/Y'        : Item(verb='Adding'),
    'X/Y/Z'      : Item(verb='Adding'),
    'X/Y/Z/iota' : Item(verb='Adding'),
    })

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

  # Add the moved files
  expected_status.add({
    'X'           : Item(status='  ', wc_rev=2),
    'X/Y'         : Item(status='  ', wc_rev=2),
    'X/Y/Z'       : Item(status='  ', wc_rev=2),
    'X/Y/Z/iota'  : Item(status='  ', wc_rev=2),
    })

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


#----------------------------------------------------------------------
# Test copying and creating parents from the repo to the wc

def copy_make_parents_repo_wc(sbox):
  "svn cp --parents URL WC_PATH"

  sbox.build()
  wc_dir = sbox.wc_dir

  iota_url = sbox.repo_url + '/iota'
  new_iota_path = sbox.ospath('X/Y/Z/iota')

  # Copy iota
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', '--parents',
                                     iota_url, new_iota_path)

  # Create expected output
  expected_output = svntest.wc.State(wc_dir, {
    'X'           : Item(verb='Adding'),
    'X/Y'         : Item(verb='Adding'),
    'X/Y/Z'       : Item(verb='Adding'),
    'X/Y/Z/iota'  : Item(verb='Adding'),
    })

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

  # Add the moved files
  expected_status.add({
    'X'           : Item(status='  ', wc_rev=2),
    'X/Y'         : Item(status='  ', wc_rev=2),
    'X/Y/Z'       : Item(status='  ', wc_rev=2),
    'X/Y/Z/iota'  : Item(status='  ', wc_rev=2),
    })

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


#----------------------------------------------------------------------
# Test copying and creating parents from the repo to the wc with dst
# directory being precreated and unversioned

def copy_make_parents_repo_wc_existing_unversioned_dst(sbox):
  "svn cp --parents URL WC_PATH with (ex. unver. dst)"

  sbox.build()
  wc_dir = sbox.wc_dir

  iota_url = sbox.repo_url + '/iota'
  new_iota_path = sbox.ospath('X/Y/Z/iota')
  os.makedirs(os.path.dirname(new_iota_path))

  # Copy iota
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', '--parents',
                                     iota_url, new_iota_path)

  # Create expected output
  expected_output = svntest.wc.State(wc_dir, {
    'X'           : Item(verb='Adding'),
    'X/Y'         : Item(verb='Adding'),
    'X/Y/Z'       : Item(verb='Adding'),
    'X/Y/Z/iota'  : Item(verb='Adding'),
    })

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

  # Add the moved files
  expected_status.add({
    'X'           : Item(status='  ', wc_rev=2),
    'X/Y'         : Item(status='  ', wc_rev=2),
    'X/Y/Z'       : Item(status='  ', wc_rev=2),
    'X/Y/Z/iota'  : Item(status='  ', wc_rev=2),
    })

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


#----------------------------------------------------------------------
# Test copying and creating parents from the wc to the repo

def copy_make_parents_wc_repo(sbox):
  "svn cp --parents WC_PATH URL"

  sbox.build()
  wc_dir = sbox.wc_dir

  iota_path = sbox.ospath('iota')
  new_iota_url = sbox.repo_url + '/X/Y/Z/iota'

  # Copy iota
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', '--parents',
                                     '-m', 'log msg',
                                     iota_path, new_iota_url)

  # Update to HEAD and verify disk contents
  expected_output = svntest.wc.State(wc_dir, {
    'X'           : Item(status='A '),
    'X/Y'         : Item(status='A '),
    'X/Y/Z'       : Item(status='A '),
    'X/Y/Z/iota'  : Item(status='A '),
    })

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'X'           : Item(),
    'X/Y'         : Item(),
    'X/Y/Z'       : Item(),
    'X/Y/Z/iota'  : Item(contents="This is the file 'iota'.\n"),
    })

  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'X'           : Item(status='  ', wc_rev=2),
    'X/Y'         : Item(status='  ', wc_rev=2),
    'X/Y/Z'       : Item(status='  ', wc_rev=2),
    'X/Y/Z/iota'  : Item(status='  ', wc_rev=2),
    })

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


#----------------------------------------------------------------------
# Test copying and creating parents from repo to repo

def copy_make_parents_repo_repo(sbox):
  "svn cp --parents URL URL"

  sbox.build()
  wc_dir = sbox.wc_dir

  iota_url = sbox.repo_url + '/iota'
  new_iota_url = sbox.repo_url + '/X/Y/Z/iota'

  # Copy iota
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', '--parents',
                                     '-m', 'log msg',
                                     iota_url, new_iota_url)

  # Update to HEAD and verify disk contents
  expected_output = svntest.wc.State(wc_dir, {
    'X'           : Item(status='A '),
    'X/Y'         : Item(status='A '),
    'X/Y/Z'       : Item(status='A '),
    'X/Y/Z/iota'  : Item(status='A '),
    })

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'X'           : Item(),
    'X/Y'         : Item(),
    'X/Y/Z'       : Item(),
    'X/Y/Z/iota'  : Item(contents="This is the file 'iota'.\n"),
    })

  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'X'           : Item(status='  ', wc_rev=2),
    'X/Y'         : Item(status='  ', wc_rev=2),
    'X/Y/Z'       : Item(status='  ', wc_rev=2),
    'X/Y/Z/iota'  : Item(status='  ', wc_rev=2),
    })

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

# Test for issue #2894
# Can't perform URL to WC copy if URL needs URI encoding.
@Issue(2894)
def URI_encoded_repos_to_wc(sbox):
  "copy a URL that needs URI encoding to WC"

  sbox.build()
  wc_dir = sbox.wc_dir
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_disk = svntest.main.greek_state.copy()

  def path_join(head, tail):
    if not head: return tail
    if not tail: return head
    return head + '/' + tail

  def greek_file_item(path):
    if path[-1:].islower():
      basename = re.sub('.*/', '', path)
      return Item("This is the file '" + basename + "'.\n")
    return Item()

  A_paths = [
    "",
    "B",
    "B/lambda",
    "B/E",
    "B/E/alpha",
    "B/E/beta",
    "B/F",
    "mu",
    "C",
    "D",
    "D/gamma",
    "D/G",
    "D/G/pi",
    "D/G/rho",
    "D/G/tau",
    "D/H",
    "D/H/chi",
    "D/H/omega",
    "D/H/psi",
    ]

  def copy_URL_to_WC(URL_rel_path, dest_name, rev):
    expected = svntest.verify.UnorderedOutput(
      [ "A         " + sbox.ospath(path_join(dest_name, p)) + "\n"
        for p in A_paths ])
    expected_status.add(
      { path_join(dest_name, p) : Item(status='  ', wc_rev=rev)
        for p in A_paths })
    expected_disk.add(
      { path_join(dest_name, p) : greek_file_item(p)
        for p in A_paths })

    # Make a copy
    svntest.actions.run_and_verify_svn(expected, [],
                                       'copy',
                                       sbox.repo_url + '/' + URL_rel_path,
                                       os.path.join(wc_dir,
                                                    dest_name))

    expected_output = svntest.wc.State(wc_dir,
                                       {dest_name : Item(verb='Adding')})
    svntest.actions.run_and_verify_commit(wc_dir,
                                          expected_output,
                                          expected_status)

  copy_URL_to_WC('A', 'A COPY', 2)
  copy_URL_to_WC('A COPY', 'A_COPY_2', 3)

#----------------------------------------------------------------------
# Issue #3068: copy source parent may be unversioned
@Issue(3068)
def allow_unversioned_parent_for_copy_src(sbox):
  "copy wc in unversioned parent to other wc"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  # Make the "other" working copy
  wc2_dir = sbox.add_wc_path('other')
  svntest.actions.duplicate_dir(wc_dir, wc2_dir)
  copy_to_path = sbox.ospath('A/copy_of_wc2')

  # Copy the wc-in-unversioned-parent working copy to our original wc.
  svntest.actions.run_and_verify_svn(None,
                                     [],
                                     'cp',
                                     wc2_dir,
                                     copy_to_path)

def unneeded_parents(sbox):
  "svn cp --parents FILE_URL DIR_URL"

  # In this message...
  #
  #    http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=138738
  #    From: Alexander Kitaev <Alexander.Kitaev@svnkit.com>
  #    To: dev@subversion.tigris.org
  #    Subject: 1.5.x segmentation fault on Repos to Repos copy
  #    Message-ID: <4830332A.6060301@svnkit.com>
  #    Date: Sun, 18 May 2008 15:46:18 +0200
  #
  # ...Alexander Kitaev describes the bug:
  #
  #    svn cp --parents SRC_FILE_URL DST_DIR_URL -m "message"
  #
  #    SRC_FILE_URL - existing file
  #    DST_DIR_URL - existing directory
  #
  #    Omitting "--parents" option makes above copy operation work as
  #    expected.
  #
  #    Bug is in libsvn_client/copy.c:801, where "dir" should be
  #    checked for null before using it in svn_ra_check_path call.
  #
  # At first we couldn't reproduce it, but later he added this:
  #
  #   Looks like there is one more condition to reproduce the problem -
  #   dst URL should has no more segments count than source one.
  #
  # In other words, if we had "/A/B" below instead of "/A" (adjusting
  # expected_* accordingly, of course), the bug wouldn't reproduce.

  sbox.build()
  wc_dir = sbox.wc_dir

  iota_url = sbox.repo_url + '/iota'
  A_url = sbox.repo_url + '/A'

  # The --parents is unnecessary, but should still work (not segfault).
  svntest.actions.run_and_verify_svn(None, [], 'cp', '--parents',
                                     '-m', 'log msg', iota_url, A_url)

  # Verify that it worked.
  expected_output = svntest.wc.State(wc_dir, {
    'A/iota' : Item(status='A '),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/iota'  : Item(contents="This is the file 'iota'.\n"),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'A/iota'  : Item(status='  ', wc_rev=2),
    })
  svntest.actions.run_and_verify_update(
    wc_dir, expected_output, expected_disk, expected_status)


def double_parents_with_url(sbox):
  "svn cp --parents URL/src_dir URL/dst_dir"

  sbox.build()
  wc_dir = sbox.wc_dir

  E_url = sbox.repo_url + '/A/B/E'
  Z_url = sbox.repo_url + '/A/B/Z'

  # --parents shouldn't result in a double commit of the same directory.
  svntest.actions.run_and_verify_svn(None, [], 'cp', '--parents',
                                     '-m', 'log msg', E_url, Z_url)

  # Verify that it worked.
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/Z/alpha' : Item(status='A '),
    'A/B/Z/beta'  : Item(status='A '),
    'A/B/Z'       : Item(status='A '),
    })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'A/B/Z/alpha' : Item(contents="This is the file 'alpha'.\n"),
    'A/B/Z/beta'  : Item(contents="This is the file 'beta'.\n"),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({
    'A/B/Z/alpha' : Item(status='  ', wc_rev=2),
    'A/B/Z/beta'  : Item(status='  ', wc_rev=2),
    'A/B/Z'       : Item(status='  ', wc_rev=2),
    })
  svntest.actions.run_and_verify_update(
    wc_dir, expected_output, expected_disk, expected_status)


# Used to cause corruption not fixable by 'svn cleanup'.
def copy_into_missing_dir(sbox):
  "copy file into missing dir"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  A_path = sbox.ospath('A')
  iota_path = sbox.ospath('iota')

  # Remove 'A'
  svntest.main.safe_rmtree(A_path)

  # Copy into the now-missing dir.  This used to give this error:
  #     svn: In directory '.'
  #     svn: Error processing command 'modify-entry' in '.'
  #     svn: Error modifying entry for 'A'
  #     svn: Entry 'A' is already under version control
  svntest.actions.run_and_verify_svn(None, ".*: Path '.*' is not a directory",
                                     'cp', iota_path, A_path)

  # 'cleanup' should not error.
  svntest.actions.run_and_verify_svn(None, [],
                                     'cleanup', wc_dir)


def find_copyfrom_information_upstairs(sbox):
  "renaming inside a copied subtree shouldn't hang"

  # The final command in this series would cause the client to hang...
  #
  #    ${SVN} cp A A2
  #    cd A2/B
  #    ${SVN} mkdir blah
  #    ${SVN} mv lambda blah
  #
  # ...because it wouldn't walk up past "" to find copyfrom information
  # (which would be in A2/.svn/entries, not on A2/B/.svn/entries).
  # Instead, it would keep thinking the parent of "" is "", and so
  # loop forever, gobbling a little bit more memory with each iteration.
  #
  # Two things fixed this:
  #
  #   1) The client walks upward beyond CWD now, so it finds the
  #      copyfrom information.
  #
  #   2) Even if we do top out at "" without finding copyfrom information
  #      (say, because someone has corrupted their working copy), we'll
  #      still detect it and error, thus breaking the loop.
  #
  # This only tests case (1).  We could test that (2) gets the expected
  # error ("no parent with copyfrom information found above 'lambda'"),
  # but we'd need to chroot to the top of the working copy or manually
  # corrupt the wc by removing the copyfrom lines from A2/.svn/entries.

  sbox.build()
  wc_dir = sbox.wc_dir

  A_path = sbox.ospath('A')
  A2_path = sbox.ospath('A2')
  B2_path = os.path.join(A2_path, 'B')

  svntest.actions.run_and_verify_svn(None, [], 'cp', A_path, A2_path)
  saved_cwd = os.getcwd()
  try:
    os.chdir(B2_path)
    svntest.actions.run_and_verify_svn(None, [], 'mkdir', 'blah')
    svntest.actions.run_and_verify_svn(None, [], 'mv', 'lambda', 'blah')
  finally:
    os.chdir(saved_cwd)

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

def change_case_of_hostname(input):
  "Change the case of the hostname, try uppercase first"

  m = re.match(r"^(.*://)([^/]*)(.*)", input)
  if m:
    scheme = m.group(1)
    host = m.group(2).upper()
    if host == m.group(2):
      host = m.group(2).lower()

    path = m.group(3)

  return scheme + host + path

# regression test for issue #2475 - move file and folder
@Issue(2475)
def path_move_and_copy_between_wcs_2475(sbox):
  "issue #2475 - move and copy between working copies"
  sbox.build()

  # checkout a second working copy, use repository url with different case
  wc2_dir = sbox.add_wc_path('2')
  repo_url2 = change_case_of_hostname(sbox.repo_url)

  expected_output = svntest.main.greek_state.copy()
  expected_output.wc_dir = wc2_dir
  expected_output.tweak(status='A ', contents=None)

  expected_wc = svntest.main.greek_state

  # Do a checkout, and verify the resulting output and disk contents.
  svntest.actions.run_and_verify_checkout(repo_url2,
                                          wc2_dir,
                                          expected_output,
                                          expected_wc)

  # Copy a file from wc to wc2
  mu_path = sbox.ospath('A/mu')
  E_path = os.path.join(wc2_dir, 'A', 'B', 'E')

  svntest.main.run_svn(None, 'cp', mu_path, E_path)

  # Copy a folder from wc to wc2
  C_path = sbox.ospath('A/C')
  B_path = os.path.join(wc2_dir, 'A', 'B')

  svntest.main.run_svn(None, 'cp', C_path, B_path)

  # Move a file from wc to wc2
  mu_path = sbox.ospath('A/mu')
  B_path = os.path.join(wc2_dir, 'A', 'B')

  svntest.main.run_svn(None, 'mv', mu_path, B_path)

  # Move a folder from wc to wc2
  C_path = sbox.ospath('A/C')
  D_path = os.path.join(wc2_dir, 'A', 'D')

  svntest.main.run_svn(None, 'mv', C_path, D_path)

  # Verify modified status
  expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 1)
  expected_status.tweak('A/mu', 'A/C', status='D ')
  svntest.actions.run_and_verify_status(sbox.wc_dir, expected_status)
  expected_status2 = svntest.actions.get_virginal_state(wc2_dir, 1)
  expected_status2.add({ 'A/B/mu' :
                        Item(status='A ', copied='+', wc_rev='-') })
  expected_status2.add({ 'A/B/C' :
                        Item(status='A ', copied='+', wc_rev='-') })
  expected_status2.add({ 'A/B/E/mu' :
                        Item(status='A ', copied='+', wc_rev='-') })
  expected_status2.add({ 'A/D/C' :
                        Item(status='A ', copied='+', wc_rev='-') })
  svntest.actions.run_and_verify_status(wc2_dir, expected_status2)


# regression test for issue #2475 - direct copy in the repository
# this test handles the 'direct move' case too, that uses the same code.
@Issue(2475)
def path_copy_in_repo_2475(sbox):
  "issue #2475 - direct copy in the repository"
  sbox.build()

  repo_url2 = change_case_of_hostname(sbox.repo_url)

  # Copy a file from repo to repo2
  mu_url = sbox.repo_url + '/A/mu'
  E_url = repo_url2 + '/A/B/E'

  svntest.main.run_svn(None, 'cp', mu_url, E_url, '-m', 'copy mu to /A/B/E')

  # For completeness' sake, update to HEAD, and verify we have a full
  # greek tree again, all at revision 2.
  expected_output = svntest.wc.State(sbox.wc_dir, {
    'A/B/E/mu'  : Item(status='A '),
    })

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({'A/B/E/mu' : Item("This is the file 'mu'.\n") })

  expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 2)
  expected_status.add({'A/B/E/mu' : Item(status='  ', wc_rev=2) })
  svntest.actions.run_and_verify_update(sbox.wc_dir,
                                        expected_output,
                                        expected_disk,
                                        expected_status)

def commit_copy_depth_empty(sbox):
  "copy a wcdir, then commit it with --depth empty"
  sbox.build()

  a = sbox.ospath('A')
  new_a = sbox.ospath('new_A')

  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', a, new_a)

  svntest.actions.run_and_verify_svn(None, [], 'ci',
                                     new_a, '--depth', 'empty',
                                     '-m', 'Copied directory')

def copy_below_copy(sbox):
  "copy a dir below a copied dir"
  sbox.build()

  A = sbox.ospath('A')
  new_A = sbox.ospath('new_A')
  new_A_D = os.path.join(new_A, 'D')
  new_A_new_D = os.path.join(new_A, 'new_D')
  new_A_mu = os.path.join(new_A, 'mu')
  new_A_new_mu = os.path.join(new_A, 'new_mu')

  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', A, new_A)

  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', new_A_D, new_A_new_D)
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', new_A_mu, new_A_new_mu)

  expected_output = svntest.wc.State(sbox.wc_dir, {
      'new_A'             : Item(verb='Adding'),
      'new_A/new_D'       : Item(verb='Adding'),
      'new_A/new_mu'      : Item(verb='Adding'),
    })
  expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 1)

  expected_status.add({
      'new_A'             : Item(status='  ', wc_rev='2'),
      'new_A/new_D'       : Item(status='  ', wc_rev='2'),
      'new_A/new_D/gamma' : Item(status='  ', wc_rev='2'),
      'new_A/new_D/G'     : Item(status='  ', wc_rev='2'),
      'new_A/new_D/G/pi'  : Item(status='  ', wc_rev='2'),
      'new_A/new_D/G/rho' : Item(status='  ', wc_rev='2'),
      'new_A/new_D/G/tau' : Item(status='  ', wc_rev='2'),
      'new_A/new_D/H'     : Item(status='  ', wc_rev='2'),
      'new_A/new_D/H/chi' : Item(status='  ', wc_rev='2'),
      'new_A/new_D/H/omega': Item(status='  ', wc_rev='2'),
      'new_A/new_D/H/psi' : Item(status='  ', wc_rev='2'),
      'new_A/D'           : Item(status='  ', wc_rev='2'),
      'new_A/D/H'         : Item(status='  ', wc_rev='2'),
      'new_A/D/H/chi'     : Item(status='  ', wc_rev='2'),
      'new_A/D/H/omega'   : Item(status='  ', wc_rev='2'),
      'new_A/D/H/psi'     : Item(status='  ', wc_rev='2'),
      'new_A/D/G'         : Item(status='  ', wc_rev='2'),
      'new_A/D/G/rho'     : Item(status='  ', wc_rev='2'),
      'new_A/D/G/pi'      : Item(status='  ', wc_rev='2'),
      'new_A/D/G/tau'     : Item(status='  ', wc_rev='2'),
      'new_A/D/gamma'     : Item(status='  ', wc_rev='2'),
      'new_A/new_mu'      : Item(status='  ', wc_rev='2'),
      'new_A/B'           : Item(status='  ', wc_rev='2'),
      'new_A/B/E'         : Item(status='  ', wc_rev='2'),
      'new_A/B/E/alpha'   : Item(status='  ', wc_rev='2'),
      'new_A/B/E/beta'    : Item(status='  ', wc_rev='2'),
      'new_A/B/F'         : Item(status='  ', wc_rev='2'),
      'new_A/B/lambda'    : Item(status='  ', wc_rev='2'),
      'new_A/C'           : Item(status='  ', wc_rev='2'),
      'new_A/mu'          : Item(status='  ', wc_rev='2'),
    })

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

def move_below_move(sbox):
  "move a dir below a moved dir"
  sbox.build()

  A = sbox.ospath('A')
  new_A = sbox.ospath('new_A')
  new_A_D = os.path.join(new_A, 'D')
  new_A_new_D = os.path.join(new_A, 'new_D')
  new_A_mu = os.path.join(new_A, 'mu')
  new_A_new_mu = os.path.join(new_A, 'new_mu')

  svntest.actions.run_and_verify_svn(None, [],
                                     'mv', A, new_A)

  svntest.actions.run_and_verify_svn(None, [],
                                     'mv', new_A_D, new_A_new_D)
  svntest.actions.run_and_verify_svn(None, [],
                                     'mv', new_A_mu, new_A_new_mu)

  expected_output = svntest.wc.State(sbox.wc_dir, {
      'A'                 : Item(verb='Deleting'),
      'new_A/D'           : Item(verb='Deleting'),
      'new_A/mu'          : Item(verb='Deleting'),
      'new_A'             : Item(verb='Adding'),
      'new_A/new_D'       : Item(verb='Adding'),
      'new_A/new_mu'      : Item(verb='Adding'),
    })
  expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 1)

  expected_status.add({
      'new_A'             : Item(status='  ', wc_rev='2'),
      'new_A/new_D'       : Item(status='  ', wc_rev='2'),
      'new_A/new_D/gamma' : Item(status='  ', wc_rev='2'),
      'new_A/new_D/G'     : Item(status='  ', wc_rev='2'),
      'new_A/new_D/G/pi'  : Item(status='  ', wc_rev='2'),
      'new_A/new_D/G/rho' : Item(status='  ', wc_rev='2'),
      'new_A/new_D/G/tau' : Item(status='  ', wc_rev='2'),
      'new_A/new_D/H'     : Item(status='  ', wc_rev='2'),
      'new_A/new_D/H/chi' : Item(status='  ', wc_rev='2'),
      'new_A/new_D/H/omega': Item(status='  ', wc_rev='2'),
      'new_A/new_D/H/psi' : Item(status='  ', wc_rev='2'),
      'new_A/new_mu'      : Item(status='  ', wc_rev='2'),
      'new_A/B'           : Item(status='  ', wc_rev='2'),
      'new_A/B/E'         : Item(status='  ', wc_rev='2'),
      'new_A/B/E/alpha'   : Item(status='  ', wc_rev='2'),
      'new_A/B/E/beta'    : Item(status='  ', wc_rev='2'),
      'new_A/B/F'         : Item(status='  ', wc_rev='2'),
      'new_A/B/lambda'    : Item(status='  ', wc_rev='2'),
      'new_A/C'           : Item(status='  ', wc_rev='2'),
    })

  expected_status.remove('A', 'A/D', 'A/D/gamma', '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/B', 'A/B/E',
                         'A/B/E/alpha', 'A/B/E/beta', 'A/B/F', 'A/B/lambda',
                         'A/C', 'A/mu')

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


def reverse_merge_move(sbox):
  """reverse merge move"""

  # Alias for svntest.actions.run_and_verify_svn
  rav_svn = svntest.actions.run_and_verify_svn

  wc_dir = sbox.wc_dir
  a_dir = sbox.ospath('A')
  a_repo_url = sbox.repo_url + '/A'
  sbox.build()

  # Create another working copy path and checkout.
  wc2_dir = sbox.add_wc_path('2')
  rav_svn(None, [], 'co', sbox.repo_url, wc2_dir)

  # Update working directory and ensure that we are at revision 1.
  rav_svn(exp_noop_up_out(1), [], 'up', wc_dir)

  # Add new folder and file, later commit
  new_path = os.path.join(a_dir, 'New')
  os.mkdir(new_path)
  first_path = os.path.join(new_path, 'first')
  svntest.main.file_append(first_path, 'appended first text')
  svntest.main.run_svn(None, "add", new_path)
  rav_svn(None, [], 'ci', wc_dir, '-m', 'Add new folder %s' % new_path)
  rav_svn(exp_noop_up_out(2), [], 'up', wc_dir)

  # Reverse merge to revert previous changes and commit
  rav_svn(None, [], 'merge', '-c', '-2', a_repo_url, a_dir)
  rav_svn(None, [], 'ci', '-m', 'Reverting svn merge -c -2.', a_dir)
  rav_svn(exp_noop_up_out(3), [], 'up', wc_dir)

  # Reverse merge again to undo last revert.
  rav_svn(None, [], 'merge', '-c', '-3', a_repo_url, a_dir)

  # Move new added file to another one and commit.
  second_path = os.path.join(new_path, 'second')
  rav_svn(None, [], 'move', first_path, second_path)
  rav_svn("Adding.*New|Adding.*first||Committed revision 4.", [],
          'ci', '-m',
          'Revert svn merge. svn mv %s %s.' % (first_path, second_path), a_dir)

  # Update second working copy. There was a bug (at least on the 1.6.x
  # branch) in which this update received both "first" and "second".
  expected_output = svntest.wc.State(wc2_dir, {
      'A/New'         : Item(status='A '),
      'A/New/second'  : Item(status='A '),
      })
  svntest.actions.run_and_verify_update(wc2_dir,
                                        expected_output,
                                        None,
                                        None)

@Issue(3699)
def nonrecursive_commit_of_copy(sbox):
  """commit only top of copy; check child behavior"""

  sbox.build()
  wc_dir = sbox.wc_dir

  main.run_svn(None, 'cp', sbox.ospath('A'),
               sbox.ospath('A_new'))
  main.run_svn(None, 'cp', sbox.ospath('A/D/G'),
               sbox.ospath('A_new/G_new'))
  main.run_svn(None, 'rm', sbox.ospath('A_new/C'))
  main.run_svn(None, 'rm', sbox.ospath('A_new/B/E'))

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
      'A_new'             : Item(status='A ', copied='+', wc_rev='-'),
      'A_new/D'           : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/D/G'         : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/D/G/pi'      : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/D/G/rho'     : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/D/G/tau'     : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/D/H'         : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/D/H/psi'     : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/D/H/chi'     : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/D/H/omega'   : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/D/gamma'     : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/B'           : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/B/lambda'    : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/B/E'         : Item(status='D ', copied='+', wc_rev='-'),
      'A_new/B/E/alpha'   : Item(status='D ', copied='+', wc_rev='-'),
      'A_new/B/E/beta'    : Item(status='D ', copied='+', wc_rev='-'),
      'A_new/B/F'         : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/mu'          : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/C'           : Item(status='D ', copied='+', wc_rev='-'),
      'A_new/G_new'       : Item(status='A ', copied='+', wc_rev='-'),
      'A_new/G_new/pi'    : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/G_new/rho'   : Item(status='  ', copied='+', wc_rev='-'),
      'A_new/G_new/tau'   : Item(status='  ', copied='+', wc_rev='-'),
    })


  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  expected_output = svntest.wc.State(wc_dir, {
    'A_new': Item(verb='Adding'),
    })

  # These nodes are added by the commit
  expected_status.tweak('A_new', 'A_new/D', 'A_new/D/G', 'A_new/D/G/pi',
                        'A_new/D/G/rho', 'A_new/D/G/tau', 'A_new/D/H',
                        'A_new/D/H/psi', 'A_new/D/H/chi', 'A_new/D/H/omega',
                        'A_new/D/gamma', 'A_new/B', 'A_new/B/lambda',
                        'A_new/B/F', 'A_new/mu',
                        status='  ', copied=None, wc_rev='2')

  # And these are now normal deletes, because their parent was committed.
  expected_status.tweak('A_new/C', 'A_new/B/E', 'A_new/B/E/alpha',
                        'A_new/B/E/beta', copied=None, wc_rev='2')

  # 'A_new/G_new' and everything below should still be added
  # as their operation root was not committed
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        [],
                                        wc_dir, '--depth', 'immediates')

# Regression test for issue #3474 - making a new subdir, moving files into it
# and then renaming the subdir, breaks history of the moved files.
@Issue(3474)
def copy_added_dir_with_copy(sbox):
  """copy/mv of new dir with copied file keeps history"""

  sbox.build(read_only=True)
  wc_dir = sbox.wc_dir

  new_dir = sbox.ospath('NewDir')
  new_dir2 = sbox.ospath('NewDir2')
  new_dir3 = sbox.ospath('NewDir3')

  # Alias for svntest.actions.run_and_verify_svn
  rav_svn = svntest.actions.run_and_verify_svn

  rav_svn(None, [], 'mkdir', new_dir)
  rav_svn(None, [], 'cp', sbox.ospath('A/mu'), new_dir)
  rav_svn(None, [], 'cp', new_dir, new_dir2)

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

  expected_status.add(
    {
      'NewDir'            : Item(status='A ', wc_rev='0'),
      'NewDir/mu'         : Item(status='A ', copied='+', wc_rev='-'),
      'NewDir2'           : Item(status='A ', wc_rev='0'),
      'NewDir2/mu'        : Item(status='A ', copied='+', wc_rev='-'),
    })

  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # move of added dir also retains copy history of children
  rav_svn(None, [], 'mv', new_dir, new_dir3)
  expected_status.remove('NewDir', 'NewDir/mu')
  expected_status.add(
    {
      'NewDir3'           : Item(status='A ', wc_rev='0'),
      'NewDir3/mu'        : Item(status='A ', copied='+', wc_rev='-'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)


@Issue(3303)
def copy_broken_symlink(sbox):
  """copy broken symlink"""

  ## See https://issues.apache.org/jira/browse/SVN-3303. ##

  sbox.build()
  wc_dir = sbox.wc_dir

  new_symlink = sbox.ospath('new_symlink')
  copied_symlink = sbox.ospath('copied_symlink')

  # Alias for svntest.actions.run_and_verify_svn
  rav_svn = svntest.actions.run_and_verify_svn

  sbox.simple_add_symlink('linktarget', 'new_symlink')

  rav_svn(None, [], 'cp', new_symlink, copied_symlink)

  # Check whether both new_symlink and copied_symlink are added to the
  # working copy
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)

  expected_status.add(
    {
      'new_symlink'       : Item(status='A ', wc_rev='0'),
      'copied_symlink'    : Item(status='A ', wc_rev='0'),
    })

  svntest.actions.run_and_verify_status(wc_dir, expected_status)


def move_dir_containing_move(sbox):
  """move a directory containing moved node"""

  sbox.build()
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     sbox.ospath('A/B/E/alpha'),
                                     sbox.ospath('A/B/E/alpha_moved'))

  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     sbox.ospath('A/B/F'),
                                     sbox.ospath('A/B/F_moved'))

  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     sbox.ospath('A/B'),
                                     sbox.ospath('A/B_tmp'))

  expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 1)
  expected_status.tweak('A/B', status='D ', moved_to='A/B_tmp')
  expected_status.tweak('A/B/E',
                        'A/B/E/alpha',
                        'A/B/E/beta',
                        'A/B/F',
                        'A/B/lambda',
                        status='D ')
  expected_status.add({
      'A/B_tmp'               : Item(status='A ', copied='+', wc_rev='-',
                                     moved_from='A/B'),
      # alpha has a revision that isn't reported by status.
      'A/B_tmp/E'             : Item(status='  ', copied='+', wc_rev='-'),
      'A/B_tmp/E/alpha'       : Item(status='D ', copied='+', wc_rev='-',
                                     moved_to='A/B_tmp/E/alpha_moved'),
      'A/B_tmp/E/alpha_moved' : Item(status='A ', copied='+', wc_rev='-',
                                     moved_from='A/B_tmp/E/alpha'),
      'A/B_tmp/E/beta'        : Item(status='  ', copied='+', wc_rev='-'),
      'A/B_tmp/F'             : Item(status='D ', copied='+', wc_rev='-',
                                     moved_to='A/B_tmp/F_moved'),
      'A/B_tmp/F_moved'       : Item(status='A ', copied='+', wc_rev='-',
                                     moved_from='A/B_tmp/F'),
      'A/B_tmp/lambda'        : Item(status='  ', copied='+', wc_rev='-'),
    })

  svntest.actions.run_and_verify_status(sbox.wc_dir, expected_status)

  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     sbox.ospath('A/B_tmp'),
                                     sbox.ospath('A/B_moved'))
  expected_status.tweak('A/B', moved_to='A/B_moved')
  expected_status.remove('A/B_tmp',
                         'A/B_tmp/E',
                         'A/B_tmp/E/alpha',
                         'A/B_tmp/E/alpha_moved',
                         'A/B_tmp/E/beta',
                         'A/B_tmp/F',
                         'A/B_tmp/F_moved',
                         'A/B_tmp/lambda')
  expected_status.add({
      'A/B_moved'               : Item(status='A ', copied='+', wc_rev='-',
                                       moved_from='A/B'),
      'A/B_moved/E'             : Item(status='  ', copied='+', wc_rev='-'),
      'A/B_moved/E/alpha'       : Item(status='D ', copied='+', wc_rev='-',
                                       moved_to='A/B_moved/E/alpha_moved'),
      'A/B_moved/E/alpha_moved' : Item(status='A ', copied='+', wc_rev='-',
                                       moved_from='A/B_moved/E/alpha'),
      'A/B_moved/E/beta'        : Item(status='  ', copied='+', wc_rev='-'),
      'A/B_moved/F'             : Item(status='D ', copied='+', wc_rev='-',
                                       moved_to='A/B_moved/F_moved'),
      'A/B_moved/F_moved'       : Item(status='A ', copied='+', wc_rev='-',
                                       moved_from='A/B_moved/F'),
      'A/B_moved/lambda'        : Item(status='  ', copied='+', wc_rev='-'),
    })

  svntest.actions.run_and_verify_status(sbox.wc_dir, expected_status)

  expected_output = svntest.wc.State(sbox.wc_dir, {
    'A/B'                    : Item(verb='Deleting'),
    'A/B_moved'              : Item(verb='Adding'),
    'A/B_moved/E/alpha'      : Item(verb='Deleting'),
    'A/B_moved/E/alpha_moved': Item(verb='Adding'),
    'A/B_moved/F'            : Item(verb='Deleting'),
    'A/B_moved/F_moved'      : Item(verb='Adding'),
    })

  expected_status.tweak('A/B_moved',
                        'A/B_moved/E',
                        'A/B_moved/E/alpha_moved',
                        'A/B_moved/E/beta',
                        'A/B_moved/F_moved',
                        'A/B_moved/lambda',
                        status='  ', copied=None, wc_rev='2')
  expected_status.remove('A/B',
                         'A/B/E',
                         'A/B/E/alpha',
                         'A/B/E/beta',
                         'A/B/F',
                         'A/B/lambda',
                         'A/B_moved/E/alpha',
                         'A/B_moved/F')
  expected_status.tweak('A/B_moved', 'A/B_moved/E/alpha_moved',
                        'A/B_moved/F_moved', moved_from=None)
  svntest.actions.run_and_verify_commit(sbox.wc_dir,
                                        expected_output,
                                        expected_status)

def copy_dir_with_space(sbox):
  """copy a directory with whitespace to one without"""

  sbox.build()
  wc_dir = sbox.wc_dir

  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     sbox.ospath('A/B/E'),
                                     sbox.ospath('E with spaces'))

  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     sbox.ospath('A/B/E/alpha'),
                                     sbox.ospath('E with spaces/al pha'))

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_output = svntest.wc.State(wc_dir, {
    'E with spaces'        : Item(verb='Adding'),
    'E with spaces/al pha' : Item(verb='Adding'),
    })
  expected_status.add({
    'E with spaces'        : Item(status='  ', wc_rev='2'),
    'E with spaces/alpha'  : Item(status='  ', wc_rev='2'),
    'E with spaces/beta'   : Item(status='  ', wc_rev='2'),
    'E with spaces/al pha' : Item(status='  ', wc_rev='2'),
    })
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)

  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     sbox.ospath('E with spaces'),
                                     sbox.ospath('E also spaces')
                                     )

  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     sbox.ospath('E with spaces/al pha'),
                                     sbox.ospath('E also spaces/al b')
                                     )

  expected_output = svntest.wc.State(wc_dir, {
      'E also spaces'     : Item(verb='Adding'),
      'E also spaces/al b': Item(verb='Adding'),
    })
  expected_status.add({
      'E also spaces'     : Item(status='  ', wc_rev='3'),
      'E also spaces/beta': Item(status='  ', wc_rev='3'),
      'E also spaces/al b': Item(status='  ', wc_rev='3'),
      'E also spaces/alpha': Item(status='  ', wc_rev='3'),
      'E also spaces/al pha': Item(status='  ', wc_rev='3'),
    })
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)

  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     sbox.ospath('E with spaces'),
                                     sbox.ospath('E new spaces')
                                     )

  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     sbox.ospath('E new spaces/al pha'),
                                     sbox.ospath('E also spaces/al c')
                                     )

  expected_output = svntest.wc.State(wc_dir, {
      'E with spaces'     : Item(verb='Deleting'),
      'E also spaces/al c': Item(verb='Adding'),
      'E new spaces'      : Item(verb='Adding'),
      'E new spaces/al pha': Item(verb='Deleting'),
  })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
      'E also spaces'     : Item(status='  ', wc_rev='3'),
      'E also spaces/beta': Item(status='  ', wc_rev='3'),
      'E also spaces/al b': Item(status='  ', wc_rev='3'),
      'E also spaces/al c': Item(status='  ', wc_rev='4'),
      'E also spaces/alpha': Item(status='  ', wc_rev='3'),
      'E also spaces/al pha': Item(status='  ', wc_rev='3'),
      'E new spaces'      : Item(status='  ', wc_rev='4'),
      'E new spaces/alpha': Item(status='  ', wc_rev='4'),
      'E new spaces/beta' : Item(status='  ', wc_rev='4'),
  })
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)

# Regression test for issue #3676
@Issue(3676)
def changed_data_should_match_checkout(sbox):
  """changed data after commit should match checkout"""

  sbox.build()
  wc_dir = sbox.wc_dir
  A_B_E = sbox.ospath('A/B/E')
  E_new = sbox.ospath('E_new')

  verify_dir = sbox.add_wc_path('verify')

  svntest.actions.run_and_verify_svn(None, [], 'copy', A_B_E, E_new)

  sbox.simple_commit()

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

  svntest.actions.run_and_verify_svn(None, [], 'co', sbox.repo_url, verify_dir)

  was_cwd = os.getcwd()
  os.chdir(verify_dir)

  rv, verify_out, err = main.run_svn(None, 'status', '-v')

  os.chdir(was_cwd)
  os.chdir(wc_dir)
  verify_out = svntest.verify.UnorderedOutput(verify_out)
  svntest.actions.run_and_verify_svn(verify_out, [], 'status', '-v')
  os.chdir(was_cwd)

# Regression test for issue #3676 for copies including directories
@Issue(3676)
def changed_dir_data_should_match_checkout(sbox):
  """changed dir after commit should match checkout"""

  sbox.build()
  wc_dir = sbox.wc_dir
  A_B = sbox.ospath('A/B')
  B_new = sbox.ospath('B_new')

  verify_dir = sbox.add_wc_path('verify')

  svntest.actions.run_and_verify_svn(None, [], 'copy', A_B, B_new)

  sbox.simple_commit()

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

  svntest.actions.run_and_verify_svn(None, [], 'co', sbox.repo_url, verify_dir)

  was_cwd = os.getcwd()
  os.chdir(verify_dir)

  rv, verify_out, err = main.run_svn(None, 'status', '-v')

  os.chdir(was_cwd)
  os.chdir(wc_dir)
  rv, verify_out2, err = main.run_svn (None, 'status', '-v')
  os.chdir(was_cwd)

  # The order of the status output is not absolutely defined, but
  # otherwise should match
  svntest.verify.verify_outputs(None,
                                sorted(verify_out2), None,
                                sorted(verify_out), None)

def move_added_nodes(sbox):
  """move added nodes"""

  sbox.build(read_only=True)

  svntest.actions.run_and_verify_svn(None, [], 'mkdir',
                                     sbox.ospath('X'),
                                     sbox.ospath('X/Y'))

  expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 1)
  expected_status.add({
      'X'   : Item(status='A ', wc_rev='0'),
      'X/Y' : Item(status='A ', wc_rev='0'),
      })
  svntest.actions.run_and_verify_status(sbox.wc_dir, expected_status)

  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     sbox.ospath('X/Y'),
                                     sbox.ospath('X/Z'))
  expected_status.remove('X/Y')
  expected_status.add({'X/Z' : Item(status='A ', wc_rev='0')})
  svntest.actions.run_and_verify_status(sbox.wc_dir, expected_status)

  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     sbox.ospath('X/Z'),
                                     sbox.ospath('Z'))
  expected_status.remove('X/Z')
  expected_status.add({'Z' : Item(status='A ', wc_rev='0')})
  svntest.actions.run_and_verify_status(sbox.wc_dir, expected_status)

  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     sbox.ospath('Z'),
                                     sbox.ospath('X/Z'))
  expected_status.remove('Z')
  expected_status.add({'X/Z' : Item(status='A ', wc_rev='0')})
  svntest.actions.run_and_verify_status(sbox.wc_dir, expected_status)

def copy_over_deleted_dir(sbox):
  "copy a directory over a deleted directory"
  sbox.build(read_only = True)

  main.run_svn(None, 'rm', sbox.ospath('A/B'))
  main.run_svn(None, 'cp', sbox.ospath('A/D'),
               sbox.ospath('A/B'))

@Issue(3314)
def mixed_rev_copy_del(sbox):
  """copy mixed-rev and delete children"""

  sbox.build()
  wc_dir = sbox.wc_dir

  # Delete and commit A/B/E/alpha
  svntest.main.run_svn(None, 'rm', sbox.ospath('A/B/E/alpha'))
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/B/E/alpha', status='D ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/E/alpha': Item(verb='Deleting'),
    })
  expected_status.remove('A/B/E/alpha')
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)

  # Update to r2, then update A/B/E/alpha and A/B/E/beta to r1
  svntest.main.run_svn(None, 'up', wc_dir)
  expected_status.tweak(wc_rev=2)
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  expected_output = svntest.wc.State(wc_dir, {
    'A/B/E/alpha' : Item(status='A '),
  })

  expected_status.add({
    'A/B/E/alpha' : Item(status='  ', wc_rev=1),
    })
  expected_status.tweak('A/B/E/beta', wc_rev=1)
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output, None,
                                        expected_status, [], False,
                                        '-r1',
                                        sbox.ospath('A/B/E/alpha'),
                                        sbox.ospath('A/B/E/beta'))

  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Copy A/B/E to A/B/E_copy
  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     sbox.ospath('A/B/E'),
                                     sbox.ospath('A/B/E_copy'))
  expected_status.add({
    'A/B/E_copy'       : Item(status='A ', copied='+', wc_rev='-'),
    # In the entries world mixed revision copies have only a single op_root
    'A/B/E_copy/alpha' : Item(status='A ', copied='+', wc_rev='-',
                              entry_status='  '),
    'A/B/E_copy/beta'  : Item(status='A ', copied='+', wc_rev='-',
                              entry_status='  '),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Delete A/B/E_copy/alpha and A/B/E_copy/beta
  svntest.main.run_svn(None, 'rm', '--force',
                       sbox.ospath('A/B/E_copy/alpha'),
                       sbox.ospath('A/B/E_copy/beta'))
  expected_status.tweak('A/B/E_copy/alpha', 'A/B/E_copy/beta', status='D ',
                        entry_status=None)
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  expected_output = svntest.wc.State(wc_dir, {
    'A/B/E_copy'      : Item(verb='Adding'),
    'A/B/E_copy/beta' : Item(verb='Deleting'),
    })
  expected_status.tweak('A/B/E_copy', wc_rev=3, copied=None, status='  ')
  expected_status.remove('A/B/E_copy/alpha', 'A/B/E_copy/beta')
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)

def copy_delete_undo(sbox, use_revert):
  "copy, delete child, undo"

  sbox.build(read_only=True)
  wc_dir = sbox.wc_dir

  # Copy directory with children
  svntest.main.run_svn(wc_dir, 'copy',
                       sbox.ospath('A/B/E'), sbox.ospath('A/B/E-copied'))
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/B/E-copied'       : Item(status='A ', copied='+', wc_rev='-'),
    'A/B/E-copied/alpha' : Item(status='  ', copied='+', wc_rev='-'),
    'A/B/E-copied/beta'  : Item(status='  ', copied='+', wc_rev='-'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Delete a child
  svntest.main.run_svn(wc_dir, 'rm', sbox.ospath('A/B/E-copied/alpha'))
  expected_status.tweak('A/B/E-copied/alpha', status='D ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Undo the whole copy
  if (use_revert):
    svntest.main.run_svn(wc_dir, 'revert', '--recursive',
                         sbox.ospath('A/B/E-copied'))
    svntest.main.safe_rmtree(sbox.ospath('A/B/E-copied'))
  else:
    svntest.main.run_svn(wc_dir, 'rm', '--force', sbox.ospath('A/B/E-copied'))
  expected_status.remove('A/B/E-copied',
                         'A/B/E-copied/alpha',
                         'A/B/E-copied/beta')

  # Undo via revert FAILs here because a wq item remains
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Copy a directory without children.
  svntest.main.run_svn(wc_dir, 'copy',
                       sbox.ospath('A/B/F'), sbox.ospath('A/B/E-copied'))
  expected_status.add({
    'A/B/E-copied'       : Item(status='A ', copied='+', wc_rev='-'),
    })

  svntest.actions.run_and_verify_status(wc_dir, expected_status)

def copy_delete_delete(sbox):
  "copy, delete child, delete copy"
  copy_delete_undo(sbox, False)

@Issue(3784)
def copy_delete_revert(sbox):
  "copy, delete child, revert copy"
  copy_delete_undo(sbox, True)

# See also delete_replaced_file() which does the same for a file.
def delete_replace_delete(sbox):
  "delete a directory scheduled for replacement"

  sbox.build(read_only=True)
  wc_dir = sbox.wc_dir

  # Delete directory with children
  svntest.main.run_svn(wc_dir, 'rm', sbox.ospath('A/B/E'))
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/B/E', 'A/B/E/alpha', 'A/B/E/beta', status='D ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Replace with directory with different children
  svntest.main.run_svn(wc_dir, 'copy',
                       sbox.ospath('A/D/G'), sbox.ospath('A/B/E'))
  expected_status.tweak('A/B/E', status='R ', copied='+', wc_rev='-')
  expected_status.add({
    'A/B/E/pi' : Item(status='  ', copied='+', wc_rev='-'),
    'A/B/E/rho' : Item(status='  ', copied='+', wc_rev='-'),
    'A/B/E/tau' : Item(status='  ', copied='+', wc_rev='-'),
    })
  # A/B/E/alpha and A/B/E/beta show up as deleted, is that right?
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Delete replacement
  svntest.main.run_svn(wc_dir, 'rm', '--force', sbox.ospath('A/B/E'))
  expected_status.tweak('A/B/E', status='D ', copied=None, wc_rev='1')
  expected_status.remove('A/B/E/pi', 'A/B/E/rho', 'A/B/E/tau')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

A_B_children = ['A/B/lambda', 'A/B/F', 'A/B/E/alpha', 'A/B/E/beta', 'A/B/E']
A_D_children = ['A/D/gamma', '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/psi', 'A/D/H/omega']

def copy_repos_over_deleted_same_kind(sbox):
  "copy repos node over deleted node, same kind"
  sbox.build(read_only = True)
  expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 1)

  # Set up some deleted paths
  sbox.simple_rm('iota', 'A/B')
  for path in ['iota', 'A/B'] + A_B_children:
    expected_status.tweak(path, status='D ')

  # Test copying
  main.run_svn(None, 'cp', sbox.repo_url + '/A/mu', sbox.ospath('iota'))
  expected_status.tweak('iota', status='R ', wc_rev='-', copied='+')
  main.run_svn(None, 'cp', sbox.repo_url + '/A/D', sbox.ospath('A/B'))
  expected_status.tweak('A/B', status='R ', wc_rev='-', copied='+')
  for child in A_D_children:
    expected_status.add({ child.replace('A/D', 'A/B'):
                          Item(status='  ', wc_rev='-', copied='+')})
  svntest.actions.run_and_verify_status(sbox.wc_dir, expected_status)

def copy_repos_over_deleted_other_kind(sbox):
  "copy repos node over deleted node, other kind"
  sbox.build(read_only = True)
  expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 1)

  # Set up some deleted paths
  sbox.simple_rm('iota', 'A/B')
  for path in ['iota', 'A/B'] + A_B_children:
    expected_status.tweak(path, status='D ')

  # Test copying
  main.run_svn(None, 'cp', sbox.repo_url + '/iota', sbox.ospath('A/B'))
  expected_status.tweak('A/B', status='R ', wc_rev='-', copied='+')
  expected_status.remove(*A_B_children)
  main.run_svn(None, 'cp', sbox.repo_url + '/A/B', sbox.ospath('iota'))
  expected_status.tweak('iota', status='R ', wc_rev='-', copied='+')
  for child in A_B_children:
    expected_status.add({ child.replace('A/B', 'iota'):
                          Item(status='  ', wc_rev='-', copied='+')})
  svntest.actions.run_and_verify_status(sbox.wc_dir, expected_status)

def copy_wc_over_deleted_same_kind(sbox):
  "copy WC node over a deleted node, same kind"
  sbox.build(read_only = True)
  expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 1)

  # Set up some deleted paths
  sbox.simple_rm('iota', 'A/B')
  for path in ['iota', 'A/B'] + A_B_children:
    expected_status.tweak(path, status='D ')

  # Test copying
  main.run_svn(None, 'cp', sbox.ospath('A/mu'), sbox.ospath('iota'))
  expected_status.tweak('iota', status='R ', wc_rev='-', copied='+')
  main.run_svn(None, 'cp', sbox.ospath('A/D'), sbox.ospath('A/B'))
  expected_status.tweak('A/B', status='R ', wc_rev='-', copied='+')
  for child in A_D_children:
    expected_status.add({ child.replace('A/D', 'A/B'):
                          Item(status='  ', wc_rev='-', copied='+')})
  svntest.actions.run_and_verify_status(sbox.wc_dir, expected_status)

def copy_wc_over_deleted_other_kind(sbox):
  "copy WC node over deleted node, other kind"
  sbox.build(read_only = True)
  expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 1)

  # Set up some deleted paths
  sbox.simple_rm('iota', 'A/B')
  for path in ['iota', 'A/B'] + A_B_children:
    expected_status.tweak(path, status='D ')

  # Test copying
  main.run_svn(None, 'cp', sbox.ospath('A/mu'), sbox.ospath('A/B'))
  expected_status.tweak('A/B', status='R ', wc_rev='-', copied='+')
  expected_status.remove(*A_B_children)
  main.run_svn(None, 'cp', sbox.ospath('A/D'), sbox.ospath('iota'))
  expected_status.tweak('iota', status='R ', wc_rev='-', copied='+')
  for child in A_D_children:
    expected_status.add({ child.replace('A/D', 'iota'):
                          Item(status='  ', wc_rev='-', copied='+')})
  svntest.actions.run_and_verify_status(sbox.wc_dir, expected_status)

def move_wc_and_repo_dir_to_itself(sbox):
  "move wc and repo dir to itself"
  sbox.build(read_only = True)
  wc_dir = sbox.ospath('A')
  repo_url = sbox.repo_url + '/A'

  # try to move wc dir to itself
  svntest.actions.run_and_verify_svn([],
                                     '.*Cannot move path.* into itself.*',
                                     'move', wc_dir, wc_dir)

  # try to move repo dir to itself
  svntest.actions.run_and_verify_svn([],
                                     '.*Cannot move URL.* into itself.*',
                                     'move', repo_url, repo_url)

@Issues(2763,3314)
def copy_wc_url_with_absent(sbox):
  "copy wc to url with several absent children"
  sbox.build()
  wc_dir = sbox.wc_dir

  # A/B a normal delete
  sbox.simple_rm('A/B')

  # A/no not-present but in HEAD
  sbox.simple_copy('A/mu', 'A/no')
  sbox.simple_commit('A/no')
  svntest.main.run_svn(None, 'up', '-r', '1', sbox.ospath('A/no'))

  # A/mu not-present and not in HEAD
  sbox.simple_rm('A/mu')
  sbox.simple_commit('A/mu')

  # A/D excluded
  svntest.main.run_svn(None, 'up', '--set-depth', 'exclude',
                       sbox.ospath('A/D'))

  # Test issue #3314 after copy
  sbox.simple_copy('A', 'A_copied')
  svntest.main.run_svn(None, 'ci', sbox.ospath('A_copied'),
                       '-m', 'Commit A_copied')

  # This tests issue #2763
  svntest.main.run_svn(None, 'cp', sbox.ospath('A'),
                       '^/A_tagged', '-m', 'Tag A')

  # And perform a normal commit
  svntest.main.run_svn(None, 'ci', sbox.ospath('A'),
                       '-m', 'Commit A')

  expected_output = svntest.wc.State(wc_dir, {
    'A_tagged'          : Item(status='A '),
    'A_tagged/D'        : Item(status='A '),
    'A_tagged/D/gamma'  : Item(status='A '),
    'A_tagged/D/H'      : Item(status='A '),
    'A_tagged/D/H/psi'  : Item(status='A '),
    'A_tagged/D/H/chi'  : Item(status='A '),
    'A_tagged/D/H/omega': Item(status='A '),
    'A_tagged/D/G'      : Item(status='A '),
    'A_tagged/D/G/pi'   : Item(status='A '),
    'A_tagged/D/G/rho'  : Item(status='A '),
    'A_tagged/D/G/tau'  : Item(status='A '),
    'A_tagged/C'        : Item(status='A '),

    'A/no'              : Item(status='A '),
    })

  # This should bring in A_tagged and A/no
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        None,
                                        None)

  # And now bring in the excluded nodes from A and A_copied
  expected_output = svntest.wc.State(wc_dir, {
    'A/D'               : Item(status='A '),
    'A/D/G'             : Item(status='A '),
    'A/D/G/pi'          : Item(status='A '),
    'A/D/G/tau'         : Item(status='A '),
    'A/D/G/rho'         : Item(status='A '),
    'A/D/H'             : Item(status='A '),
    'A/D/H/psi'         : Item(status='A '),
    'A/D/H/chi'         : Item(status='A '),
    'A/D/H/omega'       : Item(status='A '),
    'A/D/gamma'         : Item(status='A '),

    'A_copied/D'        : Item(status='A '),
    'A_copied/D/H'      : Item(status='A '),
    'A_copied/D/H/omega': Item(status='A '),
    'A_copied/D/H/psi'  : Item(status='A '),
    'A_copied/D/H/chi'  : Item(status='A '),
    'A_copied/D/G'      : Item(status='A '),
    'A_copied/D/G/tau'  : Item(status='A '),
    'A_copied/D/G/rho'  : Item(status='A '),
    'A_copied/D/G/pi'   : Item(status='A '),
    'A_copied/D/gamma'  : Item(status='A '),
  })
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        None,
                                        None,
                                        [], False,
                                        wc_dir, '--set-depth', 'infinity')

  # Except for A/no, the 3 directories should now have the same children

  items = {
    ''                  : Item(status='  ', wc_rev='6'),
    'C'                 : Item(status='  ', wc_rev='6'),
    'D'                 : Item(status='  ', wc_rev='6'),
    'D/gamma'           : Item(status='  ', wc_rev='6'),
    'D/H'               : Item(status='  ', wc_rev='6'),
    'D/H/psi'           : Item(status='  ', wc_rev='6'),
    'D/H/chi'           : Item(status='  ', wc_rev='6'),
    'D/H/omega'         : Item(status='  ', wc_rev='6'),
    'D/G'               : Item(status='  ', wc_rev='6'),
    'D/G/pi'            : Item(status='  ', wc_rev='6'),
    'D/G/tau'           : Item(status='  ', wc_rev='6'),
    'D/G/rho'           : Item(status='  ', wc_rev='6'),
  }

  expected_status = svntest.wc.State(sbox.ospath('A_copied'), items)
  svntest.actions.run_and_verify_status(sbox.ospath('A_copied'),
                                        expected_status)

  expected_status = svntest.wc.State(sbox.ospath('A_tagged'), items)
  svntest.actions.run_and_verify_status(sbox.ospath('A_tagged'),
                                        expected_status)

  expected_status.add({
    'no'                : Item(status='  ', wc_rev='6')
  })

  expected_status = svntest.wc.State(sbox.ospath('A'), items)
  svntest.actions.run_and_verify_status(sbox.ospath('A'),
                                        expected_status)


def copy_url_shortcut(sbox):
  "copy using URL shortcut source"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  # Can't use ^/A/D/G shortcut here because wc/X is unversioned.
  svntest.actions.run_and_verify_svn(None, [], 'copy',
                                     sbox.ospath('A/D/G'), sbox.ospath('X'))

  svntest.actions.run_and_verify_svn(None, [], 'rm',
                                     sbox.ospath('X/pi'))

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'X'     : Item(status='A ', copied='+', wc_rev='-'),
    'X/pi'  : Item(status='D ', copied='+', wc_rev='-'),
    'X/rho' : Item(status='  ', copied='+', wc_rev='-'),
    'X/tau' : Item(status='  ', copied='+', wc_rev='-'),
    })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Can use ^/A/D/G even though X/pi is a delete within a copy.
  svntest.actions.run_and_verify_svn(None, [], 'copy',
                                     '^/A/D/G/pi', sbox.ospath('X/pi'))

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'X'     : Item(status='A ', copied='+', wc_rev='-'),
    'X/pi'  : Item(status='R ', copied='+', wc_rev='-', entry_status='  '),
    'X/rho' : Item(status='  ', copied='+', wc_rev='-'),
    'X/tau' : Item(status='  ', copied='+', wc_rev='-'),
    })

  svntest.actions.run_and_verify_status(wc_dir, expected_status)


# Regression test for issue #3865: 'svn' on Windows cannot address
# scheduled-for-delete file, if another file differing only in case is
# present on disk
@Issue(3865)
def deleted_file_with_case_clash(sbox):
  """address a deleted file hidden by case clash"""

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  iota_path = sbox.ospath('iota')
  iota2_path = sbox.ospath('iota2')
  IOTA_path = sbox.ospath('IOTA')
  iota_url = sbox.repo_url + '/iota'

  # Perform a case-only rename in two steps.
  svntest.main.run_svn(None, 'move', iota_path, iota2_path)
  svntest.main.run_svn(None, 'move', iota2_path, IOTA_path)

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'iota' : Item(status='D ', wc_rev=1, moved_to='IOTA'),
    'IOTA' : Item(status='A ', copied='+', wc_rev='-', moved_from='iota'),
    })

  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Perform 'info' calls on both the deleted and added paths, to see if
  # we get the correct information. The deleted path is not on disk and
  # hidden by the on-disk case-clashing file, but we should be able to
  # target it explicitly because it's in the wc-db.
  expected_info_iota = {'Path' : re.escape(iota_path),
                        'Schedule' : 'delete',
                        'Copied From URL': None,
                       }
  svntest.actions.run_and_verify_info([expected_info_iota], iota_path)

  expected_info_IOTA = {'Path' : re.escape(IOTA_path),
                        'Schedule' : 'add',
                        'Copied From URL': iota_url,
                       }
  svntest.actions.run_and_verify_info([expected_info_IOTA], IOTA_path)

def copy_base_of_deleted(sbox):
  """copy -rBASE deleted"""

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  svntest.actions.run_and_verify_svn(None, [], 'rm', sbox.ospath('A/mu'))
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='D ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  svntest.actions.run_and_verify_svn(None, [], 'cp', '-rBASE',
                                     sbox.ospath('A/mu'), sbox.ospath('A/mu2'))
  expected_status.add({
    'A/mu2' : Item(status='A ', copied='+', wc_rev='-'),
    })


# Regression test for issue #3702: Unable to perform case-only rename
# on windows.
@Issue(3702)
# APR's apr_filepath_merge() with APR_FILEPATH_TRUENAME is broken on OS X.
@XFail(svntest.main.is_os_darwin)
def case_only_rename(sbox):
  """case-only rename"""

  sbox.build()
  wc_dir = sbox.wc_dir

  iota_path = sbox.ospath('iota')
  IoTa_path = sbox.ospath('IoTa')
  B_path = sbox.ospath('A/B')
  b_path = sbox.ospath('A/b')

  # Perform a couple of case-only renames.
  svntest.main.run_svn(None, 'move', iota_path, IoTa_path)
  svntest.main.run_svn(None, 'move', B_path, b_path)

  # Create expected status.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({

    'iota'              : Item(status='D ', wc_rev=1, moved_to='IoTa'),
    'IoTa'              : Item(status='A ', copied='+', wc_rev='-', moved_from='iota'),
    'A/B'               : Item(status='D ', wc_rev='1', moved_to='A/b'),
    'A/B/lambda'        : Item(status='D ', wc_rev='1'),
    'A/B/E'             : Item(status='D ', wc_rev='1'),
    'A/B/E/alpha'       : Item(status='D ', wc_rev='1'),
    'A/B/E/beta'        : Item(status='D ', wc_rev='1'),
    'A/B/F'             : Item(status='D ', wc_rev='1'),
    'A/b'               : Item(status='A ', copied='+', wc_rev='-', moved_from='A/B'),
    'A/b/E'             : Item(status='  ', copied='+', wc_rev='-'),
    'A/b/E/beta'        : Item(status='  ', copied='+', wc_rev='-'),
    'A/b/E/alpha'       : Item(status='  ', copied='+', wc_rev='-'),
    'A/b/F'             : Item(status='  ', copied='+', wc_rev='-'),
    'A/b/lambda'        : Item(status='  ', copied='+', wc_rev='-'),
    })

  # Test that the necessary deletes and adds are present in status.
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

@XFail()
@Issue(3899)
def copy_and_move_conflicts(sbox):
  """copy and move conflicts"""

  # The destination of a copy or move operation should *not* be
  # conflicted, and should contain the "mine-full" contents.

  sbox.build()
  wc = sbox.ospath
  def url(relpath):
    return '/'.join([sbox.repo_url, relpath])

  # Create an assortment of conflicts.
  #   text                                 A/B/E/alpha
  #   text (resolved by deleting markers)  A/B/E/alpha
  #   property (dir)                       A/D/H
  #   property (file)                      A/D/H/chi
  #   tree: local delete, incoming edit    A/D/gamma
  #   tree: local edit, incoming delete    A/D/G
  #   tree: local add, incoming add        A/Q
  #   tree: local missing, incoming edit   A/B/E/sigma

  ### As we improve tree-conflict handling, this test may need some
  ### maintenance.

  # Create a branch for merging.
  run_svn(None, 'cp', url('A'), url('A2'), '-m', make_log_msg()) # r2
  sbox.simple_update()

  # This revision won't be included in the merge, producing a "local
  # missing" tree conflict.
  file_write(wc('A2/B/E/sigma'), "New for merge.\n")
  sbox.simple_add('A2/B/E/sigma')

  sbox.simple_commit('A2') # r3

  # Make "incoming" changes in A2 for the merge
  # incoming edits
  file_append(wc('A2/B/E/alpha'), "Edit for merge\n")
  file_append(wc('A2/B/E/beta'), "Edit for merge\n")
  file_append(wc('A2/B/E/sigma'), "Edit for merge\n")
  sbox.simple_propset('foo', '99', 'A2/D/H')
  sbox.simple_propset('foo', '99', 'A2/D/H/chi')
  # incoming add
  sbox.simple_mkdir('A2/Q')
  file_write(wc('A2/Q/zeta'), "New for merge\n")
  sbox.simple_add('A2/Q/zeta')

  sbox.simple_commit('A2') # r4

  # Make some "local" changes in A before the merge.
  # local edit
  file_append(wc('A/B/E/alpha'), "Local edit\n")
  file_append(wc('A/B/E/beta'), "Local edit\n")
  sbox.simple_propset('foo', '100', 'A/D/H')
  sbox.simple_propset('foo', '100', 'A/D/H/chi')
  # local add
  sbox.simple_mkdir('A/Q')
  file_write(wc('A/Q/sigma'), "New local file\n")
  sbox.simple_add('A/Q/sigma')

  # Make some "incoming" changes in A before the update.
  # incoming edit
  file_append(wc('A/D/gamma'), "Edit for merge\n")
  # incoming delete
  sbox.simple_rm('A/D/G')

  sbox.simple_commit('A') # r5

  # Roll back, make local, uncommitted changes.
  run_svn(None, 'up', '-r', 4, sbox.wc_dir)
  # local delete
  sbox.simple_rm('A/D/gamma')
  # local edit
  file_append(wc('A/D/G/rho'), "Local edit\n")

  # Update to reveal the "local {delete,edit'}" tree conflicts,
  # which we can't yet catch when merging.
  sbox.simple_update()

  # Merge just one revision to reveal more conflicts.
  run_svn(None, 'merge', '-c', 4, url('A2'), wc('A'))

  # Resolve one text conflict via marker file deletion.
  os.remove(wc('A/B/E/beta.merge-left.r3'))
  os.remove(wc('A/B/E/beta.working'))
  os.remove(wc('A/B/E/beta'))
  os.rename(wc('A/B/E/beta.merge-right.r4'), wc('A/B/E/beta'))

  # Prepare for local copies and moves.
  sbox.simple_mkdir('copy-dest')
  sbox.simple_mkdir('move-dest')

  # Copy conflict victims.
  sbox.simple_copy('A/B/E/alpha', 'copy-dest')
  sbox.simple_copy('A/D/H', 'copy-dest')
  sbox.simple_copy('A/D/G', 'copy-dest')
  sbox.simple_copy('A/Q', 'copy-dest')

  # Copy directories with conflicted children.
  sbox.simple_copy('A/B', 'copy-dest')
  sbox.simple_copy('A/D', 'copy-dest')

  # Everything copied without conflicts.  The entry_status for D/G is
  # for 1.6 compatibility (see notes/api-errata/1.7/wc003.xt).
  expected_status = svntest.wc.State(wc('copy-dest'), {
    ''                  : Item(status='A ', wc_rev=0),
    'B'                 : Item(status='A ', copied='+', wc_rev='-'),
    'B/E'               : Item(status='  ', copied='+', wc_rev='-'),
    'B/E/alpha'         : Item(status='  ', copied='+', wc_rev='-'),
    'B/E/beta'          : Item(status='M ', copied='+', wc_rev='-'),
    'B/F'               : Item(status='  ', copied='+', wc_rev='-'),
    'B/lambda'          : Item(status='  ', copied='+', wc_rev='-'),
    'D'                 : Item(status='A ', copied='+', wc_rev='-'),
    'D/G'               : Item(status='A ', copied='+', wc_rev='-',
                               entry_status='  '),
    'D/G/pi'            : Item(status='  ', copied='+', wc_rev='-'),
    'D/G/rho'           : Item(status='M ', copied='+', wc_rev='-'),
    'D/G/tau'           : Item(status='  ', copied='+', wc_rev='-'),
    'D/H'               : Item(status='  ', copied='+', wc_rev='-'),
    'D/H/chi'           : Item(status='  ', copied='+', wc_rev='-'),
    'D/H/omega'         : Item(status='  ', copied='+', wc_rev='-'),
    'D/H/psi'           : Item(status='  ', copied='+', wc_rev='-'),
    'D/gamma'           : Item(status='D ', copied='+', wc_rev='-'),
    'G'                 : Item(status='A ', copied='+', wc_rev='-'),
    'G/pi'              : Item(status='  ', copied='+', wc_rev='-'),
    'G/rho'             : Item(status='M ', copied='+', wc_rev='-'),
    'G/tau'             : Item(status='  ', copied='+', wc_rev='-'),
    'H'                 : Item(status='A ', copied='+', wc_rev='-'),
    'H/chi'             : Item(status='  ', copied='+', wc_rev='-'),
    'H/omega'           : Item(status='  ', copied='+', wc_rev='-'),
    'H/psi'             : Item(status='  ', copied='+', wc_rev='-'),
    'Q'                 : Item(status='A ', copied='+', wc_rev='-'),
    'Q/sigma'           : Item(status='  ', copied='+', wc_rev='-'),
    'alpha'             : Item(status='A ', copied='+', wc_rev='-'),
    })
  svntest.actions.run_and_verify_status(wc('copy-dest'), expected_status)

  # Only the local changes appear at the copy destinations.  Note that
  # B/E/beta had been resolved via marker-file deletion before the copy.
  expected_disk = svntest.wc.State('', {
    'B/E/alpha'         : Item(contents="This is the file 'alpha'.\n"
                               "Local edit\n"),
    'B/E/beta'          : Item(contents="This is the file 'beta'.\n"
                               "Edit for merge\n"),
    'B/F'               : Item(),
    'B/lambda'          : Item(contents="This is the file 'lambda'.\n"),
    'D/G/pi'            : Item(contents="This is the file 'pi'.\n"),
    'D/G/rho'           : Item(contents="This is the file 'rho'.\n"
                               "Local edit\n"),
    'D/G/tau'           : Item(contents="This is the file 'tau'.\n"),
    'D/H'               : Item(props={'foo':'100'}),
    'D/H/chi'           : Item(contents="This is the file 'chi'.\n",
                               props={'foo':'100'}),
    'D/H/omega'         : Item(contents="This is the file 'omega'.\n"),
    'D/H/psi'           : Item(contents="This is the file 'psi'.\n"),
    'G/pi'              : Item(contents="This is the file 'pi'.\n"),
    'G/rho'             : Item(contents="This is the file 'rho'.\n"
                               "Local edit\n"),
    'G/tau'             : Item(contents="This is the file 'tau'.\n"),
    'H'                 : Item(props={'foo':'100'}),
    'H/chi'             : Item(contents="This is the file 'chi'.\n",
                               props={'foo':'100'}),
    'H/omega'           : Item(contents="This is the file 'omega'.\n"),
    'H/psi'             : Item(contents="This is the file 'psi'.\n"),
    'Q/sigma'           : Item(contents="New local file\n"),
    'alpha'             : Item(contents="This is the file 'alpha'.\n"
                               "Local edit\n"),
    })
  svntest.actions.verify_disk(wc('copy-dest'), expected_disk, True)

  # Move conflict victims.
  sbox.simple_move('A/B/E/alpha', 'move-dest')
  sbox.simple_move('A/D/H', 'move-dest')
  sbox.simple_move('A/D/G', 'move-dest')
  sbox.simple_move('A/Q', 'move-dest')

  # Move directories with conflicted children.
  sbox.simple_move('A/B', 'move-dest')
  sbox.simple_move('A/D', 'move-dest')

  # Expect same status and disk content as at the copy destination, except
  # that A/B/E/alpha, A/D/G, and A/D/H were moved away first.
  expected_status.wc_dir = wc('move-dest')
  expected_status.tweak('B/E/alpha',
                        'D/H',
                        'D/H/chi',
                        'D/H/omega',
                        'D/H/psi',
                        status='D ')
  # A/D/G had been re-added from r4 due to a "local edit, incoming delete"
  # tree conflict, so moving it away has a different effect.
  expected_status.remove('D/G',
                         'D/G/pi',
                         'D/G/rho',
                         'D/G/tau')
  expected_status.tweak('B', moved_from='../A/B')
  expected_status.tweak('D', moved_from='../A/D')
  expected_status.tweak('H', moved_from='D/H')
  expected_status.tweak('Q', moved_from='../A/Q')
  expected_status.tweak('D/H', moved_to='H')
  expected_status.tweak('alpha', moved_from='B/E/alpha')
  expected_status.tweak('B/E/alpha', moved_to='alpha')
  svntest.actions.run_and_verify_status(wc('move-dest'), expected_status)

  expected_disk = svntest.wc.State('', {
    'B/E/beta'          : Item(contents="This is the file 'beta'.\n"
                               "Edit for merge\n"),
    'B/lambda'          : Item(contents="This is the file 'lambda'.\n"),
    'B/F'               : Item(),
    'H'                 : Item(props={'foo':'100'}),
    'H/chi'             : Item(contents="This is the file 'chi'.\n",
                               props={'foo':'100'}),
    'H/psi'             : Item(contents="This is the file 'psi'.\n"),
    'H/omega'           : Item(contents="This is the file 'omega'.\n"),
    'D'                 : Item(),
    'G/tau'             : Item(contents="This is the file 'tau'.\n"),
    'G/rho'             : Item(contents="This is the file 'rho'.\n"
                               "Local edit\n"),
    'G/pi'              : Item(contents="This is the file 'pi'.\n"),
    'Q/sigma'           : Item(contents="New local file\n"),
    'alpha'             : Item(contents="This is the file 'alpha'.\n"
                               "Local edit\n"),
    })
  svntest.actions.verify_disk(wc('move-dest'), expected_disk, True)

def copy_deleted_dir(sbox):
  "try to copy a deleted directory that exists"
  sbox.build(read_only = True)

  sbox.simple_rm('iota')
  sbox.simple_rm('A')

  # E145000 - SVN_ERR_NODE_UNKNOWN_KIND
  # E155035 - SVN_ERR_WC_PATH_UNEXPECTED_STATUS
  # E155010 - SVN_ERR_WC_PATH_NOT_FOUND

  svntest.actions.run_and_verify_svn(None,
                                     'svn: (E145000|E155035|E155010): ' +
                                     '(Path \'.*iota\' does not exist)|' +
                                     '(Deleted node .*iota\' copied)',
                                     'cp', sbox.ospath('iota'),
                                     sbox.ospath('new_iota'))

  svntest.actions.run_and_verify_svn(None,
                                     'svn: (E145000|E155035|E155010): ' +
                                     '(Path \'.*D\' does not exist)|' +
                                     '(Deleted node .*D\' copied)',
                                     'cp', sbox.ospath('A/D'),
                                     sbox.ospath('new_D'))

  svntest.main.file_write(sbox.ospath('iota'), 'Not iota!')
  os.mkdir(sbox.ospath('A'))
  os.mkdir(sbox.ospath('A/D'))

  # At one time these two invocations raised an assertion.
  svntest.actions.run_and_verify_svn(None,
                                     'svn: (E155035|E155010): ' +
                                     '(Path \'.*iota\' does not exist)|' +
                                     '(Deleted node.* .*iota\' can\'t be.*)',
                                     'cp', sbox.ospath('iota'),
                                     sbox.ospath('new_iota'))
  svntest.actions.run_and_verify_svn(None,
                                     'svn: (E155035|E155010): ' +
                                     '(Path \'.*D\' does not exist)|' +
                                     '(Deleted node.* .*D\' can\'t be.*)',
                                     'cp', sbox.ospath('A/D'),
                                     sbox.ospath('new_D'))

@Issue(3631)
def commit_copied_half_of_move(sbox):
  "attempt to commit the copied part of move"
  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  iota_path = sbox.ospath('iota')
  D_path = sbox.ospath('A/D')

  # iota -> A/D/iota; verify we cannot commit just A/D/iota
  svntest.actions.run_and_verify_svn(None, [], 'mv', iota_path, D_path)
  expected_error = "svn: E200009: Cannot commit '.*%s' because it was " \
                    "moved from '.*%s'" % (re.escape(sbox.ospath('A/D/iota')),
                                           re.escape(iota_path))
  svntest.actions.run_and_verify_svn(None, expected_error,
                                     'commit', '-m', 'foo',
                                     os.path.join(D_path, 'iota'))

  # verify we cannot commit just A/D
  expected_error = "svn: E200009: Cannot commit '.*%s' because it was " \
                    "moved from '.*%s'" % (re.escape(sbox.ospath('A/D/iota')),
                                           re.escape(iota_path))
  svntest.actions.run_and_verify_svn(None, expected_error,
                                     'commit', '-m', 'foo', D_path)

  # A/D -> A/C/D; verify we cannot commit just A/C
  C_path = sbox.ospath('A/C')

  svntest.actions.run_and_verify_svn(None, [], 'mv', D_path, C_path)
  expected_error = "svn: E200009: Cannot commit '.*%s' because it was moved " \
                    "from '.*%s'" % (re.escape(os.path.join(C_path, "D")),
                                     re.escape(D_path))
  svntest.actions.run_and_verify_svn(None, expected_error,
                                     'commit', '-m', 'foo', C_path)

  # A/C/D/iota -> A/iota; verify that iota's moved-from hasn't changed
  D_iota_path = sbox.ospath('A/C/D/iota')
  A_iota_path = sbox.ospath('A/iota')
  svntest.actions.run_and_verify_svn(None, [], 'mv', D_iota_path,
                                     A_iota_path)
  expected_error = "svn: E200009: Cannot commit '.*%s' because it was " \
                    "moved from '.*%s'" % (re.escape(A_iota_path),
                                           re.escape(iota_path))
  svntest.actions.run_and_verify_svn(None, expected_error,
                                     'commit', '-m', 'foo', A_iota_path)


@Issue(3631)
def commit_deleted_half_of_move(sbox):
  "attempt to commit the deleted part of move"
  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  iota_path = sbox.ospath('iota')
  A_path = sbox.ospath('A')
  D_path = sbox.ospath('A/D')

  # iota -> A/D/iota; verify we cannot commit just iota
  svntest.actions.run_and_verify_svn(None, [], 'mv', iota_path, D_path)

  expected_error = "svn: E200009: Cannot commit '.*%s' because it was moved " \
                    "to '.*%s'" % (re.escape(iota_path),
                                   re.escape(os.path.join(D_path, "iota")))
  svntest.actions.run_and_verify_svn(None, expected_error,
                                     'commit', '-m', 'foo', iota_path)

  # A/D -> C; verify we cannot commit just A
  C_path = sbox.ospath('C')

  svntest.actions.run_and_verify_svn(None, [], 'mv', D_path, C_path)
  expected_error = "svn: E200009: Cannot commit '.*%s' because it was moved " \
                    "to '.*%s'" % (re.escape(D_path), re.escape(C_path))
  svntest.actions.run_and_verify_svn(None, expected_error,
                                     'commit', '-m', 'foo', A_path)

@Issue(4026)
def wc_wc_copy_incomplete(sbox):
  "wc-to-wc copy of an incomplete directory"

  sbox.build()
  wc_dir = sbox.wc_dir

  # We don't know what order the copy will do children of A/B so
  # remove files so that only subdirs remain then all children can be
  # marked incomplete.
  sbox.simple_rm('A/B/lambda')
  sbox.simple_commit()
  sbox.simple_update()

  # We don't know whether copy will do E or F first, so make both
  # incomplete
  svntest.actions.set_incomplete(sbox.ospath('A/B/E'), 2)
  svntest.actions.set_incomplete(sbox.ospath('A/B/F'), 2)

  # Copy fails with no changes to wc
  svntest.actions.run_and_verify_svn(None,
                                     'svn: E155035: Cannot handle status',
                                     'copy',
                                     sbox.ospath('A/B/E'),
                                     sbox.ospath('A/B/E2'))
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.remove('A/B/lambda')
  expected_status.tweak('A/B/E', 'A/B/F', status='! ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Copy fails part way through
  svntest.actions.run_and_verify_svn(None,
                                     'svn: E155035: Cannot handle status',
                                     'copy',
                                     sbox.ospath('A/B'),
                                     sbox.ospath('A/B2'))

  expected_status.add({
      'A/B2'        : Item(status='A ', copied='+', wc_rev='-'),
      'A/B2/E'      : Item(status='! ', wc_rev='-'),
      'A/B2/F'      : Item(status='! ', wc_rev='-'),
      })
  ### Can't get this to work as copied status of E and F in 1.6
  ### entries tree doesn't match 1.7 status tree
  #svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Commit preserves incomplete status
  expected_output = svntest.wc.State(wc_dir, {
      'A/B2': Item(verb='Adding'),
      })
  expected_status.tweak('A/B2',
                        status='  ', copied=None, wc_rev=3)
  expected_status.tweak('A/B2/E', 'A/B2/F',
                        status='! ', copied=None, wc_rev=3)
  ### E and F are status '!' but the test code ignores them?
  expected_status.remove('A/B2/E', 'A/B2/F')
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status)
  expected_status.add({
      'A/B2/E'       : Item(status='! ', wc_rev=3),
      'A/B2/F'       : Item(status='! ', wc_rev=3),
      })

  # Update makes things complete
  expected_output = svntest.wc.State(wc_dir, {
      'A/B2/E'       : Item(status='A '),
      'A/B2/E/alpha' : Item(status='A '),
      'A/B2/E/beta'  : Item(status='A '),
      'A/B2/F'       : Item(status='A '),
      })
  expected_status.tweak(wc_rev=3, status='  ')
  expected_status.add({
      'A/B2/E/alpha' : Item(status='  ', wc_rev=3),
      'A/B2/E/beta'  : Item(status='  ', wc_rev=3),
      })
  svntest.actions.run_and_verify_update(wc_dir,
                                        expected_output,
                                        None,
                                        expected_status)

def three_nested_moves(sbox):
  "three nested moves"

  sbox.build()
  wc_dir = sbox.wc_dir

  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     sbox.ospath('A/B'),
                                     sbox.ospath('A/B2'))
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     sbox.ospath('A/B2/E'),
                                     sbox.ospath('A/B2/E2'))
  svntest.actions.run_and_verify_svn(None, [], 'mv',
                                     sbox.ospath('A/B2/E2/alpha'),
                                     sbox.ospath('A/B2/E2/alpha2'))

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

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

def copy_to_unversioned_parent(sbox):
  "copy to unversioned parent"

  sbox.build()

  # This succeeds
  #svntest.actions.run_and_verify_svn(None, [], 'cp', '--parents',
  #                                   sbox.ospath('A/B'),
  #                                   sbox.ospath('New/B2'))

  # And this currently fails with The node '.*Unversioned' was not found,
  # while it should succeed or returns some error that a GUI client can use.
  os.mkdir(sbox.ospath('Unversioned'))
  svntest.actions.run_and_verify_svn(None, [], 'cp', '--parents',
                                     sbox.ospath('A/B'),
                                     sbox.ospath('Unversioned/B2'))

def copy_text_conflict(sbox):
  "copy with a text conflict should not copy markers"

  sbox.build()
  wc_dir = sbox.wc_dir

  mu_path = sbox.ospath('A/mu')
  svntest.main.file_append(mu_path, 'appended mu text')

  sbox.simple_commit()
  svntest.main.file_append(mu_path, 'appended mu text')

  sbox.simple_update(revision='1')

  svntest.actions.run_and_verify_svn(None, [], 'cp',
                                     sbox.ospath('A'),
                                     sbox.ospath('A_copied'))

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='C ')
  expected_status.add({
    # The markers in A
    'A/mu.mine'         : Item(status='? '),
    'A/mu.r1'           : Item(status='? '),
    'A/mu.r2'           : Item(status='? '),
    # And what is copied (without markers)
    'A_copied'          : Item(status='A ', copied='+', wc_rev='-'),
    'A_copied/C'        : Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/B'        : Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/B/lambda' : Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/B/E'      : Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/B/E/alpha': Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/B/E/beta' : Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/B/F'      : Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/D'        : Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/D/G'      : Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/D/G/tau'  : Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/D/G/rho'  : Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/D/G/pi'   : Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/D/H'      : Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/D/H/omega': Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/D/H/psi'  : Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/D/H/chi'  : Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/D/gamma'  : Item(status='  ', copied='+', wc_rev='-'),
    'A_copied/mu'       : Item(status='M ', copied='+', wc_rev='-'),
  })
  svntest.actions.run_and_verify_unquiet_status(wc_dir, expected_status)

@Issue(2843)
def copy_over_excluded(sbox):
  "copy on top of excluded should give error"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir

  svntest.actions.run_and_verify_svn(None, [],
                                     'update', '--set-depth', 'exclude',
                                     sbox.ospath('A/D'))

  expected_error = "svn: E155000: Path '.*D' exists.*excluded.*"

  svntest.actions.run_and_verify_svn(None, expected_error,
                                     'cp',
                                       sbox.repo_url + '/A/C',
                                       sbox.ospath('A/D'))

  expected_error = "svn: E155000: Path '.*D' exists.*excluded.*"
  svntest.actions.run_and_verify_svn(None, expected_error,
                                     'cp',
                                       sbox.ospath('A/C'),
                                       sbox.ospath('A/D'))

def copy_relocate(sbox):
  "copy from a relocated location"

  sbox.build()
  wc_dir = sbox.wc_dir

  tmp_dir, url = sbox.add_repo_path('relocated')

  shutil.copytree(sbox.repo_dir, tmp_dir)

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

  copiedpath = sbox.ospath('AA')
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', url + '/A', copiedpath)

  svntest.actions.run_and_verify_svn(None, [],
                                     'info', copiedpath)

def ext_wc_copy_deleted(sbox):
  "copy deleted tree from separate wc"

  sbox.build()

  wc_dir = sbox.wc_dir
  wc2_dir = sbox.add_wc_path('2')

  sbox.simple_rm('A/B')
  sbox.simple_commit()

  svntest.actions.run_and_verify_svn(None, [],
                                     'up', '--set-depth', 'exclude',
                                     sbox.ospath('A/D'))

  svntest.actions.run_and_verify_svn(None, [],
                                     'co', sbox.repo_url, wc2_dir, '-r', 1)

  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', sbox.path('A'), os.path.join(wc2_dir,'AA'))

  expected_output = expected_output = svntest.wc.State(wc2_dir, {
    'AA'    : Item(verb='Adding'),
    'AA/B'  : Item(verb='Deleting'),
  })

  svntest.actions.run_and_verify_commit(wc2_dir,
                                        expected_output, None)

def copy_subtree_deleted(sbox):
  "copy to-be-deleted subtree"

  sbox.build()
  wc_dir = sbox.wc_dir
  wc2_dir = sbox.add_wc_path('2')
  svntest.actions.duplicate_dir(wc_dir, wc2_dir)

  sbox.simple_rm('A/B')

  # Commit copy within a working copy
  sbox.simple_copy('A', 'AA')
  expected_output = expected_output = svntest.wc.State(wc_dir, {
    'AA'    : Item(verb='Adding'),
    'AA/B'  : Item(verb='Deleting'),
  })
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output, None, [],
                                        sbox.ospath('AA'))

  # Commit copy between working copies
  svntest.actions.run_and_verify_svn(None, [],
                                     'cp', sbox.path('A'),
                                     os.path.join(wc2_dir,'AA2'))
  expected_output = expected_output = svntest.wc.State(wc2_dir, {
    'AA2'    : Item(verb='Adding'),
    'AA2/B'  : Item(verb='Deleting'),
  })
  svntest.actions.run_and_verify_commit(wc2_dir,
                                        expected_output, None)

def resurrect_at_root(sbox):
   "resurrect directory at root"

   sbox.build(create_wc=False)

   svntest.actions.run_and_verify_svn(None, [], 'rm', sbox.repo_url + '/A',
                                      '-m', '')

   svntest.actions.run_and_verify_svn(None, [], 'cp',
                                      sbox.repo_url + '/A/D/H@1',
                                      sbox.repo_url + '/A', '-m', '')

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


# list all tests here, starting with None:
test_list = [ None,
              basic_copy_and_move_files,
              receive_copy_in_update,
              resurrect_deleted_dir,
              no_copy_overwrites,
              no_wc_copy_overwrites,
              copy_modify_commit,
              copy_files_with_properties,
              copy_delete_commit,
              mv_and_revert_directory,
              copy_preserve_executable_bit,
              wc_to_repos,
              repos_to_wc,
              copy_to_root,
              url_copy_parent_into_child,
              wc_copy_parent_into_child,
              resurrect_deleted_file,
              diff_repos_to_wc_copy,
              repos_to_wc_copy_eol_keywords,
              revision_kinds_local_source,
              copy_over_missing_file,
              repos_to_wc_1634,
              double_uri_escaping_1814,
              wc_to_wc_copy_between_different_repos,
              wc_to_wc_copy_deleted,
              url_to_non_existent_url_path,
              non_existent_url_to_url,
              old_dir_url_to_url,
              wc_copy_dir_to_itself,
              mixed_wc_to_url,
              wc_copy_replacement,
              wc_copy_replace_with_props,
              repos_to_wc_copy_replacement,
              repos_to_wc_copy_replace_with_props,
              delete_replaced_file,
              mv_unversioned_file,
              force_move,
              copy_deleted_dir_into_prefix,
              copy_copied_file_and_dir,
              move_copied_file_and_dir,
              move_moved_file_and_dir,
              move_file_within_moved_dir,
              move_file_out_of_moved_dir,
              move_dir_within_moved_dir,
              move_dir_out_of_moved_dir,
              move_file_back_and_forth,
              move_dir_back_and_forth,
              copy_move_added_paths,
              copy_added_paths_with_props,
              copy_added_paths_to_URL,
              move_to_relative_paths,
              move_from_relative_paths,
              copy_to_relative_paths,
              copy_from_relative_paths,
              move_multiple_wc,
              copy_multiple_wc,
              move_multiple_repo,
              copy_multiple_repo,
              copy_multiple_repo_wc,
              copy_multiple_wc_repo,
              copy_peg_rev_local_files,
              copy_peg_rev_local_dirs,
              copy_peg_rev_url,
              old_dir_wc_to_wc,
              copy_make_parents_wc_wc,
              copy_make_parents_wc_wc_existing_unversioned_dst,
              copy_make_parents_repo_wc,
              copy_make_parents_repo_wc_existing_unversioned_dst,
              copy_make_parents_wc_repo,
              copy_make_parents_repo_repo,
              URI_encoded_repos_to_wc,
              allow_unversioned_parent_for_copy_src,
              unneeded_parents,
              double_parents_with_url,
              copy_into_missing_dir,
              find_copyfrom_information_upstairs,
              path_move_and_copy_between_wcs_2475,
              path_copy_in_repo_2475,
              commit_copy_depth_empty,
              copy_below_copy,
              move_below_move,
              reverse_merge_move,
              nonrecursive_commit_of_copy,
              copy_added_dir_with_copy,
              copy_broken_symlink,
              move_dir_containing_move,
              copy_dir_with_space,
              changed_data_should_match_checkout,
              changed_dir_data_should_match_checkout,
              move_added_nodes,
              copy_over_deleted_dir,
              mixed_rev_copy_del,
              copy_delete_delete,
              copy_delete_revert,
              delete_replace_delete,
              copy_repos_over_deleted_same_kind,
              copy_repos_over_deleted_other_kind,
              copy_wc_over_deleted_same_kind,
              copy_wc_over_deleted_other_kind,
              move_wc_and_repo_dir_to_itself,
              copy_wc_url_with_absent,
              copy_url_shortcut,
              deleted_file_with_case_clash,
              copy_base_of_deleted,
              case_only_rename,
              copy_and_move_conflicts,
              copy_deleted_dir,
              commit_copied_half_of_move,
              commit_deleted_half_of_move,
              wc_wc_copy_incomplete,
              three_nested_moves,
              copy_to_unversioned_parent,
              copy_text_conflict,
              copy_over_excluded,
              copy_relocate,
              ext_wc_copy_deleted,
              copy_subtree_deleted,
              resurrect_at_root,
              foreign_repos_to_wc,
             ]

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


### End of file.
