The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/env python
#
#  commit_tests.py:  testing fancy commit cases.
#
#  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 sys, os, re

# Our testing module
import svntest
from svntest import wc

# (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

from svntest.main import server_has_revprop_commit, \
    server_gets_client_capabilities
from svntest.actions import inject_conflict_into_wc

######################################################################
# Utilities
#

def is_non_posix_os_or_cygwin_platform():
  return (not svntest.main.is_posix_os()) or sys.platform == 'cygwin'

def get_standard_state(wc_dir):
  """Return a status list reflecting the local mods made by
  make_standard_slew_of_changes()."""

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

  state.tweak('', 'A/D', 'A/D/G/pi', status=' M')
  state.tweak('A/B/lambda', status='M ')
  state.tweak('A/B/E', 'A/D/H/chi', status='R ')
  state.tweak('A/B/E/alpha', 'A/B/E/beta', 'A/C', 'A/D/gamma',
              'A/D/G/rho', status='D ')
  state.tweak('A/D/H/omega', status='MM')

  # New things
  state.add({
    'Q' : Item(status='A ', wc_rev=0),
    'Q/floo' : Item(status='A ', wc_rev=0),
    'A/D/H/gloo' : Item(status='A ', wc_rev=0),
    'A/B/E/bloo' : Item(status='A ', wc_rev=0),
    })

  return state


def make_standard_slew_of_changes(wc_dir):
  """Make a specific set of local mods to WC_DIR.  These will be used
  by every commit-test.  Verify the 'svn status' output, and return the
  (pre-commit) status tree."""

  # Cache current working directory, move into wc_dir
  was_cwd = os.getcwd()
  os.chdir(wc_dir)

  # Add a directory
  os.mkdir('Q')
  svntest.main.run_svn(None, 'add', 'Q')

  # Remove two directories
  A_B_E = os.path.join('A', 'B', 'E')
  svntest.main.run_svn(None, 'rm', A_B_E)
  svntest.main.run_svn(None, 'rm', os.path.join('A', 'C'))

  # Replace one of the removed directories
  # But first recreate if it doesn't exist (single-db)
  if not os.path.exists(A_B_E):
    os.mkdir(A_B_E)
  svntest.main.run_svn(None, 'add', A_B_E)

  # Make property mods to two directories
  svntest.main.run_svn(None, 'propset', 'foo', 'bar', os.curdir)
  svntest.main.run_svn(None, 'propset', 'foo2', 'bar2', os.path.join('A', 'D'))

  # Add three files
  svntest.main.file_append(os.path.join('A', 'B', 'E', 'bloo'), "hi")
  svntest.main.file_append(os.path.join('A', 'D', 'H', 'gloo'), "hello")
  svntest.main.file_append(os.path.join('Q', 'floo'), "yo")
  svntest.main.run_svn(None, 'add', os.path.join('A', 'B', 'E', 'bloo'))
  svntest.main.run_svn(None, 'add', os.path.join('A', 'D', 'H', 'gloo'))
  svntest.main.run_svn(None, 'add', os.path.join('Q', 'floo'))

  # Remove three files
  svntest.main.run_svn(None, 'rm', os.path.join('A', 'D', 'G', 'rho'))
  svntest.main.run_svn(None, 'rm', os.path.join('A', 'D', 'H', 'chi'))
  svntest.main.run_svn(None, 'rm', os.path.join('A', 'D', 'gamma'))

  # Replace one of the removed files
  svntest.main.file_append(os.path.join('A', 'D', 'H', 'chi'), "chi")
  svntest.main.run_svn(None, 'add', os.path.join('A', 'D', 'H', 'chi'))

  # Make textual mods to two files
  svntest.main.file_append(os.path.join('A', 'B', 'lambda'), "new ltext")
  svntest.main.file_append(os.path.join('A', 'D', 'H', 'omega'), "new otext")

  # Make property mods to three files
  svntest.main.run_svn(None, 'propset', 'blue', 'azul',
                       os.path.join('A', 'D', 'H', 'omega'))
  svntest.main.run_svn(None, 'propset', 'green', 'verde',
                       os.path.join('Q', 'floo'))
  svntest.main.run_svn(None, 'propset', 'red', 'rojo',
                       os.path.join('A', 'D', 'G', 'pi'))

  # Restore the CWD.
  os.chdir(was_cwd)

  # Build an expected status tree.
  expected_status = get_standard_state(wc_dir)

  # Verify status -- all local mods should be present.
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  return expected_status


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


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

def commit_one_file(sbox):
  "commit one file"

  sbox.build()
  wc_dir = sbox.wc_dir

  expected_status = make_standard_slew_of_changes(wc_dir)

  omega_path = os.path.join(wc_dir, 'A', 'D', 'H', 'omega')

  # Create expected state.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/H/omega' : Item(verb='Sending'),
    })
  expected_status.tweak('A/D/H/omega', wc_rev=2, status='  ')

  # Commit the one file.
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        omega_path)


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

def commit_one_new_file(sbox):
  "commit one newly added file"

  sbox.build()
  wc_dir = sbox.wc_dir

  expected_status = make_standard_slew_of_changes(wc_dir)

  gloo_path = os.path.join(wc_dir, 'A', 'D', 'H', 'gloo')

  # Create expected state.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/H/gloo' : Item(verb='Adding'),
    })
  expected_status.tweak('A/D/H/gloo', wc_rev=2, status='  ')

  # Commit the one file.
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        gloo_path)


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

def commit_one_new_binary_file(sbox):
  "commit one newly added binary file"

  sbox.build()
  wc_dir = sbox.wc_dir

  expected_status = make_standard_slew_of_changes(wc_dir)

  gloo_path = os.path.join(wc_dir, 'A', 'D', 'H', 'gloo')
  svntest.main.run_svn(None, 'propset', 'svn:mime-type',
                       'application/octet-stream', gloo_path)

  # Create expected state.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/H/gloo' : Item(verb='Adding  (bin)'),
    })
  expected_status.tweak('A/D/H/gloo', wc_rev=2, status='  ')

  # Commit the one file.
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        gloo_path)


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

def commit_multiple_targets(sbox):
  "commit multiple targets"

  sbox.build()
  wc_dir = sbox.wc_dir

  # This test will commit three targets:  psi, B, and pi.  In that order.

  # Make local mods to many files.
  AB_path = os.path.join(wc_dir, 'A', 'B')
  lambda_path = os.path.join(wc_dir, 'A', 'B', 'lambda')
  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
  pi_path = os.path.join(wc_dir, 'A', 'D', 'G', 'pi')
  omega_path = os.path.join(wc_dir, 'A', 'D', 'H', 'omega')
  psi_path = os.path.join(wc_dir, 'A', 'D', 'H', 'psi')
  svntest.main.file_append(lambda_path, 'new appended text for lambda')
  svntest.main.file_append(rho_path, 'new appended text for rho')
  svntest.main.file_append(pi_path, 'new appended text for pi')
  svntest.main.file_append(omega_path, 'new appended text for omega')
  svntest.main.file_append(psi_path, 'new appended text for psi')

  # Just for kicks, add a property to A/D/G as well.  We'll make sure
  # that it *doesn't* get committed.
  ADG_path = os.path.join(wc_dir, 'A', 'D', 'G')
  svntest.main.run_svn(None, 'propset', 'foo', 'bar', ADG_path)

  # Create expected output tree for 'svn ci'.  We should see changes
  # only on these three targets, no others.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/H/psi' : Item(verb='Sending'),
    'A/B/lambda' : Item(verb='Sending'),
    'A/D/G/pi' : Item(verb='Sending'),
    })

  # Create expected status tree; all local revisions should be at 1,
  # but our three targets should be at 2.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/H/psi', 'A/B/lambda', 'A/D/G/pi', wc_rev=2)

  # rho and omega should still display as locally modified:
  expected_status.tweak('A/D/G/rho', 'A/D/H/omega', status='M ')

  # A/D/G should still have a local property set, too.
  expected_status.tweak('A/D/G', status=' M')

  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        psi_path, AB_path, pi_path)

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


def commit_multiple_targets_2(sbox):
  "commit multiple targets, 2nd variation"

  sbox.build()
  wc_dir = sbox.wc_dir

  # This test will commit four targets:  psi, B, omega and pi.  In that order.

  # Make local mods to many files.
  AB_path = os.path.join(wc_dir, 'A', 'B')
  lambda_path = os.path.join(wc_dir, 'A', 'B', 'lambda')
  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
  pi_path = os.path.join(wc_dir, 'A', 'D', 'G', 'pi')
  omega_path = os.path.join(wc_dir, 'A', 'D', 'H', 'omega')
  psi_path = os.path.join(wc_dir, 'A', 'D', 'H', 'psi')
  svntest.main.file_append(lambda_path, 'new appended text for lambda')
  svntest.main.file_append(rho_path, 'new appended text for rho')
  svntest.main.file_append(pi_path, 'new appended text for pi')
  svntest.main.file_append(omega_path, 'new appended text for omega')
  svntest.main.file_append(psi_path, 'new appended text for psi')

  # Just for kicks, add a property to A/D/G as well.  We'll make sure
  # that it *doesn't* get committed.
  ADG_path = os.path.join(wc_dir, 'A', 'D', 'G')
  svntest.main.run_svn(None, 'propset', 'foo', 'bar', ADG_path)

  # Created expected output tree for 'svn ci'.  We should see changes
  # only on these three targets, no others.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/H/psi' : Item(verb='Sending'),
    'A/B/lambda' : Item(verb='Sending'),
    'A/D/H/omega' : Item(verb='Sending'),
    'A/D/G/pi' : Item(verb='Sending'),
    })

  # Create expected status tree; all local revisions should be at 1,
  # but our four targets should be at 2.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/H/psi', 'A/B/lambda', 'A/D/G/pi', 'A/D/H/omega',
                        wc_rev=2)

  # rho should still display as locally modified:
  expected_status.tweak('A/D/G/rho', status='M ')

  # A/D/G should still have a local property set, too.
  expected_status.tweak('A/D/G', status=' M')

  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        psi_path, AB_path,
                                        omega_path, pi_path)

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

def commit_inclusive_dir(sbox):
  "commit wc_dir/A/D -- includes D. (anchor=A, tgt=D)"

  sbox.build()
  wc_dir = sbox.wc_dir

  expected_status = make_standard_slew_of_changes(wc_dir)

  D_path = os.path.join(wc_dir, 'A', 'D')

  # Create expected state.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D' : Item(verb='Sending'),
    'A/D/G/pi' : Item(verb='Sending'),
    'A/D/G/rho' : Item(verb='Deleting'),
    'A/D/H/gloo' : Item(verb='Adding'),
    'A/D/H/chi' : Item(verb='Replacing'),
    'A/D/H/omega' : Item(verb='Sending'),
    'A/D/gamma' : Item(verb='Deleting'),
    })

  expected_status.remove('A/D/G/rho', 'A/D/gamma')
  expected_status.tweak('A/D', 'A/D/G/pi', 'A/D/H/omega',
                        wc_rev=2, status='  ')
  expected_status.tweak('A/D/H/chi', 'A/D/H/gloo', wc_rev=2, status='  ')

  # Commit the one file.
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        D_path)

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

def commit_top_dir(sbox):
  "commit wc_dir -- (anchor=wc_dir, tgt={})"

  sbox.build()
  wc_dir = sbox.wc_dir

  expected_status = make_standard_slew_of_changes(wc_dir)

  # Create expected state.
  expected_output = svntest.wc.State(wc_dir, {
    '' : Item(verb='Sending'),
    'Q' : Item(verb='Adding'),
    'Q/floo' : Item(verb='Adding'),
    'A/B/E' : Item(verb='Replacing'),
    'A/B/E/bloo' : Item(verb='Adding'),
    'A/B/lambda' : Item(verb='Sending'),
    'A/C' : Item(verb='Deleting'),
    'A/D' : Item(verb='Sending'),
    'A/D/G/pi' : Item(verb='Sending'),
    'A/D/G/rho' : Item(verb='Deleting'),
    'A/D/H/gloo' : Item(verb='Adding'),
    'A/D/H/chi' : Item(verb='Replacing'),
    'A/D/H/omega' : Item(verb='Sending'),
    'A/D/gamma' : Item(verb='Deleting'),
    })

  expected_status.remove('A/D/G/rho', 'A/D/gamma', 'A/C',
                         'A/B/E/alpha', 'A/B/E/beta')
  expected_status.tweak('A/D', 'A/D/G/pi', 'A/D/H/omega', 'Q/floo', '',
                        wc_rev=2, status='  ')
  expected_status.tweak('A/D/H/chi', 'Q', 'A/B/E', 'A/B/E/bloo', 'A/B/lambda',
                        'A/D/H/gloo', wc_rev=2, status='  ')

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

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

# Regression test for bug reported by Jon Trowbridge:
#
#    From: Jon Trowbridge <trow@ximian.com>
#    Subject:  svn segfaults if you commit a file that hasn't been added
#    To: dev@subversion.tigris.org
#    Date: 17 Jul 2001 03:20:55 -0500
#    Message-Id: <995358055.16975.5.camel@morimoto>
#
#    The problem is that report_single_mod in libsvn_wc/adm_crawler.c is
#    called with its entry parameter as NULL, but the code doesn't
#    check that entry is non-NULL before trying to dereference it.
#
# This bug never had an issue number.
#
def commit_unversioned_thing(sbox):
  "committing unversioned object produces error"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Create an unversioned file in the wc.
  svntest.main.file_append(os.path.join(wc_dir, 'blorg'), "nothing to see")

  # Commit a non-existent file and *expect* failure:
  svntest.actions.run_and_verify_commit(wc_dir,
                                        None,
                                        None,
                                        "is not under version control",
                                        os.path.join(wc_dir,'blorg'))

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

# regression test for bug #391

def nested_dir_replacements(sbox):
  "replace two nested dirs, verify empty contents"

  sbox.build()
  wc_dir = sbox.wc_dir

  A_D = os.path.join(wc_dir, 'A', 'D')

  # Delete and re-add A/D (a replacement), and A/D/H (another replace).
  svntest.main.run_svn(None, 'rm', A_D)

  # Recreate directories for single-db
  if not os.path.exists(A_D):
    os.mkdir(A_D)
    os.mkdir(os.path.join(A_D, 'H'))
  svntest.main.run_svn(None, 'add', '--depth=empty', A_D)
  svntest.main.run_svn(None, 'add', '--depth=empty', os.path.join(A_D, 'H'))

  # For kicks, add new file A/D/bloo.
  svntest.main.file_append(os.path.join(A_D, 'bloo'), "hi")
  svntest.main.run_svn(None, 'add', os.path.join(A_D, 'bloo'))

  # Verify pre-commit status:
  #
  #    - A/D should both be scheduled as addition, A/D as "R" at rev 1
  #         (rev 1 because they both existed before at rev 1)
  #
  #    - A/D/H should be a local addition "A"
  #         (and exists as shadowed node in BASE)
  #
  #    - A/D/bloo scheduled as "A" at rev 0
  #         (rev 0 because it did not exist before)
  #
  #    - ALL other children of A/D scheduled as "D" at rev 1

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D', status='R ', wc_rev=1)
  # In the entries world we couldn't represent H properly, so it shows
  # A/D/H as a replacement against BASE
  expected_status.tweak('A/D/H', status='A ', wc_rev='-',
                                 entry_status='R ', entry_rev='1')
  expected_status.add({
    'A/D/bloo' : Item(status='A ', wc_rev=0),
    })
  expected_status.tweak('A/D/G', 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau',
                        'A/D/H/chi', 'A/D/H/omega', 'A/D/H/psi', 'A/D/gamma',
                        status='D ')

  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Build expected post-commit trees:

  # Create expected output tree.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D' : Item(verb='Replacing'),
    'A/D/H' : Item(verb='Adding'),
    'A/D/bloo' : Item(verb='Adding'),
    })

  # Created expected status tree.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D', 'A/D/H', wc_rev=2)
  expected_status.add({
    'A/D/bloo' : Item(status='  ', wc_rev=2),
    })
  expected_status.remove('A/D/G', 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau',
                        'A/D/H/chi', 'A/D/H/omega', 'A/D/H/psi', 'A/D/gamma')

  # Commit from the top of the working copy and verify output & status.
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        wc_dir)

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

# Testing part 1 of the "Greg Hudson" problem -- specifically, that
# our use of the "existence=deleted" flag is working properly in cases
# where the parent directory's revision lags behind a deleted child's
# revision.

def hudson_part_1(sbox):
  "hudson prob 1.0:  delete file, commit, update"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Remove gamma from the working copy.
  gamma_path = os.path.join(wc_dir, 'A', 'D', 'gamma')
  svntest.main.run_svn(None, 'rm', gamma_path)

  # Create expected commit output.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/gamma' : Item(verb='Deleting'),
    })

  # After committing, status should show no sign of gamma.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/D/gamma')

  # Commit the deletion of gamma and verify.
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None, wc_dir)

  # Now gamma should be marked as `deleted' under the hood.  When we
  # update, we should no output, and a perfect, virginal status list
  # at revision 2.  (The `deleted' entry should be removed.)

  # Expected output of update:  nothing.
  expected_output = svntest.wc.State(wc_dir, {})

  # Expected disk tree:  everything but gamma
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/D/gamma')

  # Expected status after update:  totally clean revision 2, minus gamma.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.remove('A/D/gamma')

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


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

# Testing part 1 of the "Greg Hudson" problem -- variation on previous
# test, removing a directory instead of a file this time.

def hudson_part_1_variation_1(sbox):
  "hudson prob 1.1:  delete dir, commit, update"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Remove H from the working copy.
  H_path = os.path.join(wc_dir, 'A', 'D', 'H')
  svntest.main.run_svn(None, 'rm', H_path)

  # Create expected commit output.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/H' : Item(verb='Deleting'),
    })

  # After committing, status should show no sign of H or its contents
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/D/H', 'A/D/H/chi', 'A/D/H/omega', 'A/D/H/psi')

  # Commit the deletion of H and verify.
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None, wc_dir)

  # Now H should be marked as `deleted' under the hood.  When we
  # update, we should no see output, and a perfect, virginal status
  # list at revision 2.  (The `deleted' entry should be removed.)

  # Expected output of update:  H gets a no-op deletion.
  expected_output = svntest.wc.State(wc_dir, {})

  # Expected disk tree:  everything except files in H
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/D/H', 'A/D/H/chi', 'A/D/H/omega', 'A/D/H/psi')

  # Expected status after update:  totally clean revision 2, minus H.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.remove('A/D/H', 'A/D/H/chi', 'A/D/H/omega', 'A/D/H/psi')

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

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

# Testing part 1 of the "Greg Hudson" problem -- variation 2.  In this
# test, we make sure that a file that is BOTH `deleted' and scheduled
# for addition can be correctly committed & merged.

def hudson_part_1_variation_2(sbox):
  "hudson prob 1.2:  delete, commit, re-add, commit"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Remove gamma from the working copy.
  gamma_path = os.path.join(wc_dir, 'A', 'D', 'gamma')
  svntest.main.run_svn(None, 'rm', gamma_path)

  # Create expected commit output.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/gamma' : Item(verb='Deleting'),
    })

  # After committing, status should show no sign of gamma.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/D/gamma')

  # Commit the deletion of gamma and verify.
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None, wc_dir)

  # Now gamma should be marked as `deleted' under the hood.
  # Go ahead and re-add gamma, so that is *also* scheduled for addition.
  svntest.main.file_append(gamma_path, "added gamma")
  svntest.main.run_svn(None, 'add', gamma_path)

  # For sanity, examine status: it should show a revision 2 tree with
  # gamma scheduled for addition.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/gamma', wc_rev=0, status='A ')

  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Create expected commit output.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/gamma' : Item(verb='Adding'),
    })

  # After committing, status should show only gamma at revision 3.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/gamma', wc_rev=3)

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


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

# Testing part 2 of the "Greg Hudson" problem.
#
# In this test, we make sure that we're UNABLE to commit a propchange
# on an out-of-date directory.

def hudson_part_2(sbox):
  "hudson prob 2.0:  prop commit on old dir fails"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Remove gamma from the working copy.
  D_path = os.path.join(wc_dir, 'A', 'D')
  gamma_path = os.path.join(wc_dir, 'A', 'D', 'gamma')
  svntest.main.run_svn(None, 'rm', gamma_path)

  # Create expected commit output.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/gamma' : Item(verb='Deleting'),
    })

  # After committing, status should show no sign of gamma.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/D/gamma')

  # Commit the deletion of gamma and verify.
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None, wc_dir)

  # Now gamma should be marked as `deleted' under the hood, at
  # revision 2.  Meanwhile, A/D is still lagging at revision 1.

  # Make a propchange on A/D
  svntest.main.run_svn(None, 'ps', 'foo', 'bar', D_path)

  # Commit and *expect* a repository Merge failure:
  svntest.actions.run_and_verify_commit(wc_dir,
                                        None,
                                        None,
                                        "[Oo]ut.of.date",
                                        wc_dir)

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

# Test a possible regression in our 'deleted' post-commit handling.
#
# This test moves files from one subdir to another, commits, then
# updates the empty directory.  Nothing should be printed, assuming
# all the moved files are properly marked as 'deleted' and reported to
# the server.

def hudson_part_2_1(sbox):
  "hudson prob 2.1:  move files, update empty dir"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Move all the files in H to G
  H_path = os.path.join(wc_dir, 'A', 'D', 'H')
  G_path = os.path.join(wc_dir, 'A', 'D', 'G')
  chi_path = os.path.join(H_path, 'chi')
  psi_path = os.path.join(H_path, 'psi')
  omega_path = os.path.join(H_path, 'omega')

  svntest.main.run_svn(None, 'mv', chi_path, G_path)
  svntest.main.run_svn(None, 'mv', psi_path, G_path)
  svntest.main.run_svn(None, 'mv', omega_path, G_path)

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

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/D/H/chi')
  expected_status.remove('A/D/H/omega')
  expected_status.remove('A/D/H/psi')
  expected_status.add({ 'A/D/G/chi' :
                        Item(wc_rev=2, status='  ') })
  expected_status.add({ 'A/D/G/omega' :
                        Item(wc_rev=2, status='  ') })
  expected_status.add({ 'A/D/G/psi' :
                        Item(wc_rev=2, status='  ') })

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

  # Now, assuming all three files in H are marked as 'deleted', an
  # update of H should print absolutely nothing.
  expected_output = svntest.wc.State(wc_dir, { })

  # Reuse expected_status
  expected_status.tweak(wc_rev=2)

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

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

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

def hook_test(sbox):
  "hook testing"

  sbox.build()

  # Get paths to the working copy and repository
  wc_dir = sbox.wc_dir
  repo_dir = sbox.repo_dir

  # Create a hook that appends its name to a log file.
  hook_format = """import sys
fp = open(sys.argv[1] + '/hooks.log', 'a')
fp.write("%s\\n")
fp.close()"""

  # Setup the hook configs to log data to a file
  start_commit_hook = svntest.main.get_start_commit_hook_path(repo_dir)
  svntest.main.create_python_hook_script(start_commit_hook,
                                         hook_format % "start_commit_hook")

  pre_commit_hook = svntest.main.get_pre_commit_hook_path(repo_dir)
  svntest.main.create_python_hook_script(pre_commit_hook,
                                         hook_format % "pre_commit_hook")

  post_commit_hook = svntest.main.get_post_commit_hook_path(repo_dir)
  svntest.main.create_python_hook_script(post_commit_hook,
                                         hook_format % "post_commit_hook")

  # Modify iota just so there is something to commit.
  iota_path = os.path.join(wc_dir, "iota")
  svntest.main.file_append(iota_path, "More stuff in iota")

  # Commit, no output expected.
  svntest.actions.run_and_verify_svn(None, [], [],
                                     'ci', '--quiet',
                                     '-m', 'log msg', wc_dir)

  # Now check the logfile
  expected_data = [ 'start_commit_hook\n', 'pre_commit_hook\n', 'post_commit_hook\n' ]

  logfilename = os.path.join(repo_dir, "hooks.log")
  if os.path.exists(logfilename):
    fp = open(logfilename)
  else:
    raise svntest.verify.SVNUnexpectedOutput("hook logfile %s not found")\
                                             % logfilename

  actual_data = fp.readlines()
  fp.close()
  os.unlink(logfilename)
  svntest.verify.compare_and_display_lines('wrong hook logfile content',
                                           'STDERR',
                                           expected_data, actual_data)

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

# Regression test for bug #469, whereby merge() was once reporting
# erroneous conflicts due to Ancestor < Target < Source, in terms of
# node-rev-id parentage.

def merge_mixed_revisions(sbox):
  "commit mixed-rev wc (no erroneous merge error)"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Make some convenient paths.
  iota_path = os.path.join(wc_dir, 'iota')
  H_path = os.path.join(wc_dir, 'A', 'D', 'H')
  chi_path = os.path.join(wc_dir, 'A', 'D', 'H', 'chi')
  omega_path = os.path.join(wc_dir, 'A', 'D', 'H', 'omega')

  # Here's the reproduction formula, in 5 parts.
  # Hoo, what a buildup of state!

  # 1. echo "moo" >> iota; echo "moo" >> A/D/H/chi; svn ci
  svntest.main.file_append(iota_path, "moo")
  svntest.main.file_append(chi_path, "moo")

  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(verb='Sending'),
    'A/D/H/chi' : Item(verb='Sending'),
    })

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', 'A/D/H/chi', wc_rev=2)

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


  # 2. svn up A/D/H
  expected_status = svntest.wc.State(wc_dir, {
    'A/D/H' : Item(status='  ', wc_rev=2),
    'A/D/H/chi' : Item(status='  ', wc_rev=2),
    'A/D/H/omega' : Item(status='  ', wc_rev=2),
    'A/D/H/psi' : Item(status='  ', wc_rev=2),
    })
  expected_disk = svntest.wc.State('', {
    'omega' : Item("This is the file 'omega'.\n"),
    'chi' : Item("This is the file 'chi'.\nmoo"),
    'psi' : Item("This is the file 'psi'.\n"),
    })
  expected_output = svntest.wc.State(wc_dir, { })
  svntest.actions.run_and_verify_update(H_path,
                                        expected_output,
                                        expected_disk,
                                        expected_status)


  # 3. echo "moo" >> iota; svn ci iota
  svntest.main.file_append(iota_path, "moo2")
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/H', 'A/D/H/omega', 'A/D/H/chi', 'A/D/H/psi',
                        wc_rev=2)
  expected_status.tweak('iota', wc_rev=3)

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


  # 4. echo "moo" >> A/D/H/chi; svn ci A/D/H/chi
  svntest.main.file_append(chi_path, "moo3")
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/H/chi' : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/H/chi', wc_rev=4)
  expected_status.tweak('A/D/H', 'A/D/H/omega', 'A/D/H/psi', wc_rev=2)
  expected_status.tweak('iota', wc_rev=3)
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None, wc_dir)

  # 5. echo "moo" >> iota; svn ci iota
  svntest.main.file_append(iota_path, "moomoo")
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/H', 'A/D/H/omega', 'A/D/H/psi', wc_rev=2)
  expected_status.tweak('A/D/H/chi', wc_rev=4)
  expected_status.tweak('iota', wc_rev=5)
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None, wc_dir)

  # At this point, here is what our tree should look like:
  # _    1       (     5)  working_copies/commit_tests-10
  # _    1       (     5)  working_copies/commit_tests-10/A
  # _    1       (     5)  working_copies/commit_tests-10/A/B
  # _    1       (     5)  working_copies/commit_tests-10/A/B/E
  # _    1       (     5)  working_copies/commit_tests-10/A/B/E/alpha
  # _    1       (     5)  working_copies/commit_tests-10/A/B/E/beta
  # _    1       (     5)  working_copies/commit_tests-10/A/B/F
  # _    1       (     5)  working_copies/commit_tests-10/A/B/lambda
  # _    1       (     5)  working_copies/commit_tests-10/A/C
  # _    1       (     5)  working_copies/commit_tests-10/A/D
  # _    1       (     5)  working_copies/commit_tests-10/A/D/G
  # _    1       (     5)  working_copies/commit_tests-10/A/D/G/pi
  # _    1       (     5)  working_copies/commit_tests-10/A/D/G/rho
  # _    1       (     5)  working_copies/commit_tests-10/A/D/G/tau
  # _    2       (     5)  working_copies/commit_tests-10/A/D/H
  # _    4       (     5)  working_copies/commit_tests-10/A/D/H/chi
  # _    2       (     5)  working_copies/commit_tests-10/A/D/H/omega
  # _    2       (     5)  working_copies/commit_tests-10/A/D/H/psi
  # _    1       (     5)  working_copies/commit_tests-10/A/D/gamma
  # _    1       (     5)  working_copies/commit_tests-10/A/mu
  # _    5       (     5)  working_copies/commit_tests-10/iota

  # At this point, we're ready to modify omega and iota, and commit
  # from the top.  We should *not* get a conflict!

  svntest.main.file_append(iota_path, "finalmoo")
  svntest.main.file_append(omega_path, "finalmoo")

  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(verb='Sending'),
    'A/D/H/omega' : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', 'A/D/H/omega', wc_rev=6)
  expected_status.tweak('A/D/H', 'A/D/H/psi', wc_rev=2)
  expected_status.tweak('A/D/H/chi', wc_rev=4)
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None, wc_dir)

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

def commit_uri_unsafe(sbox):
  "commit files and dirs with URI-unsafe characters"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Note: on Windows, files can't have angle brackets in them, so we
  # don't tests that case.
  if svntest.main.windows or sys.platform == 'cygwin':
    angle_name = '_angle_'
    nasty_name = '#![]{}()__%'
  else:
    angle_name = '<angle>'
    nasty_name = '#![]{}()<>%'

  # Make some convenient paths.
  hash_dir = os.path.join(wc_dir, '#hash#')
  nasty_dir = os.path.join(wc_dir, nasty_name)
  space_path = os.path.join(wc_dir, 'A', 'D', 'space path')
  bang_path = os.path.join(wc_dir, 'A', 'D', 'H', 'bang!')
  bracket_path = os.path.join(wc_dir, 'A', 'D', 'H', 'bra[ket')
  brace_path = os.path.join(wc_dir, 'A', 'D', 'H', 'bra{e')
  angle_path = os.path.join(wc_dir, 'A', 'D', 'H', angle_name)
  paren_path = os.path.join(wc_dir, 'A', 'D', 'pare)(theses')
  percent_path = os.path.join(wc_dir, '#hash#', 'percen%')
  nasty_path = os.path.join(wc_dir, 'A', nasty_name)

  os.mkdir(hash_dir)
  os.mkdir(nasty_dir)
  svntest.main.file_append(space_path, "This path has a space in it.")
  svntest.main.file_append(bang_path, "This path has a bang in it.")
  svntest.main.file_append(bracket_path, "This path has a bracket in it.")
  svntest.main.file_append(brace_path, "This path has a brace in it.")
  svntest.main.file_append(angle_path, "This path has angle brackets in it.")
  svntest.main.file_append(paren_path, "This path has parentheses in it.")
  svntest.main.file_append(percent_path, "This path has a percent in it.")
  svntest.main.file_append(nasty_path, "This path has all sorts of ick in it.")

  add_list = [hash_dir,
              nasty_dir, # not xml-safe
              space_path,
              bang_path,
              bracket_path,
              brace_path,
              angle_path, # not xml-safe
              paren_path,
              percent_path,
              nasty_path, # not xml-safe
              ]
  for item in add_list:
    svntest.main.run_svn(None, 'add', '--depth=empty', item)

  expected_output = svntest.wc.State(wc_dir, {
    '#hash#' : Item(verb='Adding'),
    nasty_name : Item(verb='Adding'),
    'A/D/space path' : Item(verb='Adding'),
    'A/D/H/bang!' : Item(verb='Adding'),
    'A/D/H/bra[ket' : Item(verb='Adding'),
    'A/D/H/bra{e' : Item(verb='Adding'),
    'A/D/H/' + angle_name : Item(verb='Adding'),
    'A/D/pare)(theses' : Item(verb='Adding'),
    '#hash#/percen%' : Item(verb='Adding'),
    'A/' + nasty_name : Item(verb='Adding'),
    })

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

  # Items in our add list will be at rev 2
  for item in expected_output.desc.keys():
    expected_status.add({ item : Item(wc_rev=2, status='  ') })

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


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

def commit_deleted_edited(sbox):
  "commit deleted yet edited files"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Make some convenient paths.
  iota_path = os.path.join(wc_dir, 'iota')
  mu_path = os.path.join(wc_dir, 'A', 'mu')

  # Edit the files.
  svntest.main.file_append(iota_path, "This file has been edited.")
  svntest.main.file_append(mu_path, "This file has been edited.")

  # Schedule the files for removal.
  svntest.main.run_svn(None, 'remove', '--force', iota_path)
  svntest.main.run_svn(None, 'remove', '--force', mu_path)

  # Make our output list
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(verb='Deleting'),
    'A/mu' : Item(verb='Deleting'),
    })

  # Items in the status list are all at rev 1, except the two things
  # we changed...but then, they don't exist at all.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('iota', 'A/mu')

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

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

def commit_in_dir_scheduled_for_addition(sbox):
  "commit a file inside dir scheduled for addition"

  sbox.build()
  wc_dir = sbox.wc_dir

  A_path = os.path.join(wc_dir, 'A')
  Z_path = os.path.join(wc_dir, 'Z')
  Z_abspath = os.path.abspath(Z_path)
  mu_path = os.path.join(wc_dir, 'Z', 'mu')

  svntest.main.run_svn(None, 'move', A_path, Z_path)
  
  # Make sure mu is a committable
  svntest.main.file_write(mu_path, "xxxx")

  # Commit a copied thing inside an added-with-history directory,
  # expecting a specific error to occur!
  svntest.actions.run_and_verify_commit(wc_dir,
                                        None,
                                        None,
                                        "svn: E200009: '" +
                                        re.escape(Z_abspath) +
                                        "' is not known to exist in the",
                                        mu_path)

  Q_path = os.path.join(wc_dir, 'Q')
  Q_abspath = os.path.abspath(Q_path)
  bloo_path = os.path.join(Q_path, 'bloo')

  os.mkdir(Q_path)
  svntest.main.file_append(bloo_path, "New contents.")
  svntest.main.run_svn(None, 'add', Q_path)

  # Commit a regular added thing inside an added directory,
  # expecting a specific error to occur!
  svntest.actions.run_and_verify_commit(wc_dir,
                                        None,
                                        None,
                                        "svn: E200009: '" +
                                        re.escape(Q_abspath) +
                                        "' is not known to exist in the",
                                        bloo_path)

  R_path = sbox.ospath('Z/B/R')
  sbox.simple_mkdir('Z/B/R')

  # Commit a d added thing inside an added directory,
  # expecting a specific error to occur!
  svntest.actions.run_and_verify_commit(wc_dir,
                                        None,
                                        None,
                                        "svn: E200009: '" +
                                        re.escape(Z_abspath) +
                                        "' is not known to exist in the.*",
                                        R_path)                                        

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

# Does this make sense now that deleted files are always removed from the wc?
def commit_rmd_and_deleted_file(sbox):
  "commit deleted (and missing) file"

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

  # 'svn remove' mu
  svntest.main.run_svn(None, 'rm', mu_path)

  # Commit, hoping to see no errors
  svntest.actions.run_and_verify_svn("Output on stderr where none expected",
                                     svntest.verify.AnyOutput, [],
                                     'commit', '-m', 'logmsg', mu_path)

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

# Issue #644 which failed over ra_neon.
@Issue(644)
def commit_add_file_twice(sbox):
  "issue 644 attempt to add a file twice"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Create a file
  gloo_path = os.path.join(wc_dir, 'A', 'D', 'H', 'gloo')
  svntest.main.file_append(gloo_path, "hello")
  svntest.main.run_svn(None, 'add', gloo_path)

  # Create expected output tree.
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/H/gloo' : Item(verb='Adding'),
    })

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

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

  # Update to state before commit
  svntest.main.run_svn(None, 'up', '-r', '1', wc_dir)

  # Create the file again
  svntest.main.file_append(gloo_path, "hello")
  svntest.main.run_svn(None, 'add', gloo_path)

  # Commit and *expect* a failure:
  svntest.actions.run_and_verify_commit(wc_dir,
                                        None,
                                        None,
                                        "already exists",
                                        wc_dir)

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

# There was a problem that committing from a directory that had a
# longer name than the working copy directory caused the commit notify
# messages to display truncated/random filenames.

def commit_from_long_dir(sbox):
  "commit from a dir with a longer name than the wc"

  sbox.build()
  wc_dir = sbox.wc_dir

  was_dir = os.getcwd()
  abs_wc_dir = os.path.realpath(os.path.join(was_dir, wc_dir))

  # something to commit
  svntest.main.file_append(os.path.join(wc_dir, 'iota'), "modified iota")

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

  # Any length name was enough to provoke the original bug, but
  # keeping its length less than that of the filename 'iota' avoided
  # random behaviour, but still caused the test to fail
  extra_name = 'xx'

  os.chdir(wc_dir)
  os.mkdir(extra_name)
  os.chdir(extra_name)

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

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

def commit_with_lock(sbox):
  "try to commit when directory is locked"

  sbox.build()
  # modify gamma and lock its directory
  wc_dir = sbox.wc_dir

  D_path = os.path.join(wc_dir, 'A', 'D')
  gamma_path = os.path.join(D_path, 'gamma')
  svntest.main.file_append(gamma_path, "modified gamma")
  svntest.actions.lock_admin_dir(D_path)

  # this commit should fail
  svntest.actions.run_and_verify_commit(wc_dir,
                                        None,
                                        None,
                                        'svn: E155004: '
                                        'Working copy \'.*\' locked',
                                        wc_dir)

  # unlock directory
  svntest.actions.run_and_verify_svn("Output on stderr where none expected",
                                     [], [],
                                     'cleanup', D_path)

  # this commit should succeed
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/gamma' : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/gamma', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        wc_dir)

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

# Explicitly commit the current directory.  This did at one point fail
# in post-commit processing due to a path canonicalization problem.

def commit_current_dir(sbox):
  "commit the current directory"

  sbox.build()

  wc_dir = sbox.wc_dir
  svntest.main.run_svn(None, 'propset', 'pname', 'pval', wc_dir)

  was_cwd = os.getcwd()

  os.chdir(wc_dir)

  expected_output = svntest.wc.State('.', {
    '.' : Item(verb='Sending'),
    })
  svntest.actions.run_and_verify_commit('.',
                                        expected_output,
                                        None,
                                        None,
                                        '.')
  os.chdir(was_cwd)

  # I can't get the status check to work as part of run_and_verify_commit.
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('', wc_rev=2, status='  ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

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

# Check that the pending txn gets removed from the repository after
# a failed commit.

def failed_commit(sbox):
  "commit with conflicts and check txn in repo"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Make the other working copy
  other_wc_dir = sbox.add_wc_path('other')
  svntest.actions.duplicate_dir(wc_dir, other_wc_dir)

  # Make different changes in the two working copies
  iota_path = os.path.join(wc_dir, "iota")
  svntest.main.file_append(iota_path, "More stuff in iota")

  other_iota_path = os.path.join(other_wc_dir, "iota")
  svntest.main.file_append(other_iota_path, "More different stuff in iota")

  # Commit both working copies. The second commit should fail.
  svntest.actions.run_and_verify_svn("Output on stderr where none expected",
                                     svntest.verify.AnyOutput, [],
                                     'commit', '-m', 'log', wc_dir)

  svntest.actions.run_and_verify_svn("Output on stderr expected",
                                     None, svntest.verify.AnyOutput,
                                     'commit', '-m', 'log', other_wc_dir)

  # Now list the txns in the repo. The list should be empty.
  exit_code, output, errput = svntest.main.run_svnadmin('lstxns',
                                                        sbox.repo_dir)
  svntest.verify.compare_and_display_lines(
    "Error running 'svnadmin lstxns'.",
    'STDERR', [], errput)
  svntest.verify.compare_and_display_lines(
    "Output of 'svnadmin lstxns' is unexpected.",
    'STDOUT', [], output)

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

# Commit from multiple working copies is being worked on as issue #2381.
# Also related to issue #959, this test here doesn't use svn:externals
# but the behaviour needs to be considered.
# In this test two WCs are nested, one WC is child of the other.
@Issue(2381)
def commit_multiple_wc_nested(sbox):
  "commit from two nested working copies"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Checkout a second working copy
  wc2_dir = os.path.join(wc_dir, 'A', 'wc2')
  url = sbox.repo_url
  svntest.actions.run_and_verify_svn("Output on stderr where none expected",
                                     svntest.verify.AnyOutput, [],
                                     'checkout',
                                     url, wc2_dir)

  # Modify both working copies
  mu_path = os.path.join(wc_dir, 'A', 'mu')
  svntest.main.file_append(mu_path, 'appended mu text')
  lambda2_path = os.path.join(wc2_dir, 'A', 'B', 'lambda')
  svntest.main.file_append(lambda2_path, 'appended lambda2 text')

  # Verify modified status
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='M ')
  svntest.actions.run_and_verify_status(wc_dir, expected_status)
  expected_status2 = svntest.actions.get_virginal_state(wc2_dir, 1)
  expected_status2.tweak('A/B/lambda', status='M ')
  svntest.actions.run_and_verify_status(wc2_dir, expected_status2)

  # Commit should succeed, even though one target is a "child" of the other.
  svntest.actions.run_and_verify_svn("Ouput on stderr where none expected",
                                     svntest.verify.AnyOutput, [],
                                     'commit', '-m', 'log',
                                     wc_dir, wc2_dir)

  # Verify status changed
  expected_status.tweak('A/mu', status='  ', wc_rev=2)
  expected_status2.tweak('A/B/lambda', status='  ', wc_rev=2)
  svntest.actions.run_and_verify_status(wc_dir, expected_status)
  svntest.actions.run_and_verify_status(wc2_dir, expected_status2)

# Same as commit_multiple_wc_nested except that the two WCs are not nested.
@Issue(2381)
def commit_multiple_wc(sbox):
  "commit from two working copies"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Cleanup original wc
  svntest.sandbox._cleanup_test_path(wc_dir)

  # Checkout two wcs
  wc1_dir = os.path.join(wc_dir, 'wc1')
  wc2_dir = os.path.join(wc_dir, 'wc2')
  url = sbox.repo_url
  svntest.actions.run_and_verify_svn("Output on stderr where none expected",
                                     svntest.verify.AnyOutput, [],
                                     'checkout',
                                     url, wc1_dir)
  svntest.actions.run_and_verify_svn("Output on stderr where none expected",
                                     svntest.verify.AnyOutput, [],
                                     'checkout',
                                     url, wc2_dir)

  # Modify both working copies
  mu1_path = os.path.join(wc1_dir, 'A', 'mu')
  svntest.main.file_append(mu1_path, 'appended mu1 text')
  lambda2_path = os.path.join(wc2_dir, 'A', 'B', 'lambda')
  svntest.main.file_append(lambda2_path, 'appended lambda2 text')

  # Verify modified status
  expected_status1 = svntest.actions.get_virginal_state(wc1_dir, 1)
  expected_status1.tweak('A/mu', status='M ')
  svntest.actions.run_and_verify_status(wc1_dir, expected_status1)
  expected_status2 = svntest.actions.get_virginal_state(wc2_dir, 1)
  expected_status2.tweak('A/B/lambda', status='M ')
  svntest.actions.run_and_verify_status(wc2_dir, expected_status2)

  # Commit should succeed.
  svntest.actions.run_and_verify_svn("Output on stderr where none expected",
                                     svntest.verify.AnyOutput, [],
                                     'commit', '-m', 'log',
                                     wc1_dir, wc2_dir)

  # Verify status changed
  expected_status1.tweak('A/mu', status='  ', wc_rev=2)
  expected_status2.tweak('A/B/lambda', status='  ', wc_rev=2)
  svntest.actions.run_and_verify_status(wc1_dir, expected_status1)
  svntest.actions.run_and_verify_status(wc2_dir, expected_status2)

# Same as commit_multiple_wc except that the two WCs come
# from different repositories. Commits to multiple repositories
# are outside the scope of issue #2381.
@Issue(2381)
def commit_multiple_wc_multiple_repos(sbox):
  "committing two WCs from different repos fails"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Create another repository
  repo2, url2 = sbox.add_repo_path("repo2")
  svntest.main.copy_repos(sbox.repo_dir, repo2, 1, 1)

  # Cleanup original wc
  svntest.sandbox._cleanup_test_path(wc_dir)

  # Checkout two wcs
  wc1_dir = os.path.join(wc_dir, 'wc1')
  wc2_dir = os.path.join(wc_dir, 'wc2')
  svntest.actions.run_and_verify_svn("Output on stderr where none expected",
                                     svntest.verify.AnyOutput, [],
                                     'checkout',
                                     sbox.repo_url, wc1_dir)
  svntest.actions.run_and_verify_svn("Output on stderr where none expected",
                                     svntest.verify.AnyOutput, [],
                                     'checkout',
                                     url2, wc2_dir)

  # Modify both working copies
  mu1_path = os.path.join(wc1_dir, 'A', 'mu')
  svntest.main.file_append(mu1_path, 'appended mu1 text')
  lambda2_path = os.path.join(wc2_dir, 'A', 'B', 'lambda')
  svntest.main.file_append(lambda2_path, 'appended lambda2 text')

  # Verify modified status
  expected_status1 = svntest.actions.get_virginal_state(wc1_dir, 1)
  expected_status1.tweak('A/mu', status='M ')
  svntest.actions.run_and_verify_status(wc1_dir, expected_status1)
  expected_status2 = svntest.actions.get_virginal_state(wc2_dir, 1)
  expected_status2.tweak('A/B/lambda', status='M ')
  svntest.actions.run_and_verify_status(wc2_dir, expected_status2)

  # Commit should fail, since WCs come from different repositories.
  # The exact error message depends on whether or not the tests are
  # run below an existing working copy
  error_re = ( ".*(is not a working copy" +
                 "|Are all targets part of the same working copy" +
                 "|was not found).*" )
  svntest.actions.run_and_verify_svn("Expected output on stderr doesn't match",
                                     [], error_re,
                                     'commit', '-m', 'log',
                                     wc1_dir, wc2_dir)

  # Verify status unchanged
  svntest.actions.run_and_verify_status(wc1_dir, expected_status1)
  svntest.actions.run_and_verify_status(wc2_dir, expected_status2)

#----------------------------------------------------------------------
@Issues(1195,1239)
def commit_nonrecursive(sbox):
  "commit named targets with -N"

  sbox.build()
  wc_dir = sbox.wc_dir

  ### Note: the original recipes used 'add -N'.  These days, we use
  ### --depth={empty,files}, and both the code and the comments below
  ### have been adjusted to reflect this.

  #####################################################
  ### Issue #1195:
  ###
  ### 1. Create these directories and files:
  ###
  ###    file1
  ###    dir1
  ###    dir1/file2
  ###    dir1/file3
  ###    dir1/dir2
  ###    dir1/dir2/file4
  ###
  ### 2. run 'svn add --depth=empty <all of the above>'
  ###
  ### 3. run 'svn ci -N <all of the above>'
  ###
  ### (The bug was that only 4 entities would get committed, when it
  ### should be 6: dir2/ and file4 were left out.)

  # These paths are relative to the top of the test's working copy.
  file1_path = 'file1'
  dir1_path  = 'dir1'
  file2_path = os.path.join('dir1', 'file2')
  file3_path = os.path.join('dir1', 'file3')
  dir2_path  = os.path.join('dir1', 'dir2')
  file4_path = os.path.join('dir1', 'dir2', 'file4')

  # Create the new files and directories.
  svntest.main.file_append(os.path.join(wc_dir, file1_path), 'this is file1')
  os.mkdir(os.path.join(wc_dir, dir1_path))
  svntest.main.file_append(os.path.join(wc_dir, file2_path), 'this is file2')
  svntest.main.file_append(os.path.join(wc_dir, file3_path), 'this is file3')
  os.mkdir(os.path.join(wc_dir, dir2_path))
  svntest.main.file_append(os.path.join(wc_dir, file4_path), 'this is file4')

  # Add them to version control.
  svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput, [],
                                     'add', '--depth=empty',
                                     os.path.join(wc_dir, file1_path),
                                     os.path.join(wc_dir, dir1_path),
                                     os.path.join(wc_dir, file2_path),
                                     os.path.join(wc_dir, file3_path),
                                     os.path.join(wc_dir, dir2_path),
                                     os.path.join(wc_dir, file4_path))

  # Commit.  We should see all 6 items (2 dirs, 4 files) get sent.
  expected_output = svntest.wc.State(
    wc_dir,
    { file1_path                    : Item(verb='Adding'),
      dir1_path                     : Item(verb='Adding'),
      file2_path                    : Item(verb='Adding'),
      file3_path                    : Item(verb='Adding'),
      dir2_path                     : Item(verb='Adding'),
      file4_path                    : Item(verb='Adding'),
      }
    )

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    file1_path   : Item(status='  ', wc_rev=2),
    dir1_path    : Item(status='  ', wc_rev=2),
    file2_path   : Item(status='  ', wc_rev=2),
    file3_path   : Item(status='  ', wc_rev=2),
    dir2_path    : Item(status='  ', wc_rev=2),
    file4_path   : Item(status='  ', wc_rev=2),
    })

  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        '-N',
                                        os.path.join(wc_dir, file1_path),
                                        os.path.join(wc_dir, dir1_path),
                                        os.path.join(wc_dir, file2_path),
                                        os.path.join(wc_dir, file3_path),
                                        os.path.join(wc_dir, dir2_path),
                                        os.path.join(wc_dir, file4_path))

  #######################################################################
  ###
  ### There's some complex history here; please bear with me.
  ###
  ### First there was issue #1239, which had the following recipe:
  ###
  ###    1. Create these directories and files:
  ###
  ###       dirA
  ###       dirA/fileA
  ###       dirA/fileB
  ###       dirA/dirB
  ###       dirA/dirB/fileC
  ###       dirA/dirB/nocommit
  ###
  ###    2. run 'svn add --depth=empty <all of the above>'
  ###
  ###    3. run 'svn ci -N <all but nocommit>'
  ###
  ###    (In this recipe, 'add -N' has been changed to 'add --depth...',
  ###     but 'ci -N' has been left as-is, for reasons explained below.)
  ###
  ### Issue #1239 claimed a two-part bug: that step 3 would try to
  ### commit the file `nocommit' when it shouldn't, and that it would
  ### get an error anyway:
  ###
  ###       Adding         wc/dirA
  ###       Adding         wc/dirA/fileA
  ###       Adding         wc/dirA/fileB
  ###       Adding         wc/dirA/dirB
  ###       Adding         wc/dirA/dirB/nocommit
  ###       Adding         wc/dirA/dirB/fileC
  ###       Transmitting file data ....svn: A problem occurred; \
  ###          see later errors for details
  ###       svn: Commit succeeded, but other errors follow:
  ###       svn: Problem running log
  ###       svn: Error bumping revisions post-commit (details follow):
  ###       svn: in directory
  ###       'F:/Programmation/Projets/subversion/svnant/test/wc/dirA'
  ###       svn: start_handler: error processing command 'committed' in
  ###       'F:/Programmation/Projets/subversion/svnant/test/wc/dirA'
  ###       svn: Working copy not locked
  ###       svn: directory not locked
  ###       (F:/Programmation/Projets/subversion/svnant/test/wc)
  ###
  ### However, this was all in the days before --depth, and depended
  ### on an idiosyncratic interpretation of -N, one which required
  ### commit to behave differently from other commands taking -N.
  ###
  ### These days, -N should be equivalent to --depth=files in almost
  ### all cases.  There are some exceptions (e.g., status), and commit
  ### is one of them: 'commit -N' means 'commit --depth=empty'.
  ###
  ### The original implementation, as well as this test, mistakenly
  ### mapped 'commit -N' to 'commit --depth=files'; that was a bug that
  ### made 'svn ci -N' incompatible with 1.4 and earlier versions.
  ###
  ### See also 'commit_propmods_with_depth_empty' in depth_tests.py .

  # Now add these directories and files, except the last:
  dirA_path  = 'dirA'
  fileA_path = os.path.join('dirA', 'fileA')
  fileB_path = os.path.join('dirA', 'fileB')
  dirB_path  = os.path.join('dirA', 'dirB')
  nope_1_path = os.path.join(dirB_path, 'nope_1')
  nope_2_path = os.path.join(dirB_path, 'nope_2')

  # Create the new files and directories.
  os.mkdir(os.path.join(wc_dir, dirA_path))
  svntest.main.file_append(os.path.join(wc_dir, fileA_path), 'fileA')
  svntest.main.file_append(os.path.join(wc_dir, fileB_path), 'fileB')
  os.mkdir(os.path.join(wc_dir, dirB_path))
  svntest.main.file_append(os.path.join(wc_dir, nope_1_path), 'nope_1')
  svntest.main.file_append(os.path.join(wc_dir, nope_2_path), 'nope_2')

  # Add them to version control.
  svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput, [],
                                     'add', '-N',
                                     os.path.join(wc_dir, dirA_path),
                                     os.path.join(wc_dir, fileA_path),
                                     # don't add fileB
                                     os.path.join(wc_dir, dirB_path),
                                     os.path.join(wc_dir, nope_1_path),
                                     # don't add nope_2
                                     )

  expected_output = svntest.wc.State(
    wc_dir,
    { dirA_path  : Item(verb='Adding'),
      # no children!
      }
    )

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

  # Expect the leftovers from the first part of the test.
  expected_status.add({
    file1_path : Item(status='  ', wc_rev=2),
    dir1_path  : Item(status='  ', wc_rev=2),
    file2_path : Item(status='  ', wc_rev=2),
    file3_path : Item(status='  ', wc_rev=2),
    dir2_path  : Item(status='  ', wc_rev=2),
    file4_path : Item(status='  ', wc_rev=2),
    })

  # Expect some commits and some non-commits from this part of the test.
  expected_status.add({
    dirA_path     : Item(status='  ', wc_rev=3),
    fileA_path    : Item(status='A ', wc_rev=0),
    # no fileB
    dirB_path     : Item(status='A ', wc_rev=0),
    nope_1_path   : Item(status='A ', wc_rev=0),
    # no nope_2
    })

  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        '-N', os.path.join(wc_dir, dirA_path))

#----------------------------------------------------------------------
# Regression for #1017: ra_neon was allowing the deletion of out-of-date
# files or dirs, which majorly violates Subversion's semantics.
# An out-of-date error should be raised if the object to be committed has
# already been deleted or modified in the repo.

def commit_out_of_date_deletions(sbox):
  "commit deletion of out-of-date file or dir"

  # Path           WC 1    WC backup
  # ===========    ====    =========
  # A/C            pset    del
  # A/I            del     pset
  # A/B/F          del     del
  # A/D/H/omega    text    del
  # A/B/E/alpha    pset    del
  # A/D/H/chi      del     text
  # A/B/E/beta     del     pset
  # A/D/H/psi      del     del

  sbox.build()
  wc_dir = sbox.wc_dir

  # Need another empty dir
  I_path = os.path.join(wc_dir, 'A', 'I')
  os.mkdir(I_path)
  svntest.main.run_svn(None, 'add', I_path)
  svntest.main.run_svn(None, 'ci', '-m', 'prep', wc_dir)
  svntest.main.run_svn(None, 'up', 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)

  # Edits in wc 1
  C_path = os.path.join(wc_dir, 'A', 'C')
  omega_path = os.path.join(wc_dir, 'A', 'D', 'H', 'omega')
  alpha_path = os.path.join(wc_dir, 'A', 'B', 'E', 'alpha')
  svntest.main.run_svn(None, 'propset', 'fooprop', 'foopropval', C_path)
  svntest.main.file_append(omega_path, 'appended omega text')
  svntest.main.run_svn(None, 'propset', 'fooprop', 'foopropval', alpha_path)

  # Deletions in wc 1
  I_path = os.path.join(wc_dir, 'A', 'I')
  F_path = os.path.join(wc_dir, 'A', 'B', 'F')
  chi_path = os.path.join(wc_dir, 'A', 'D', 'H', 'chi')
  beta_path = os.path.join(wc_dir, 'A', 'B', 'E', 'beta')
  psi_path = os.path.join(wc_dir, 'A', 'D', 'H', 'psi')
  svntest.main.run_svn(None, 'rm', I_path, F_path, chi_path, beta_path,
                       psi_path)

  # Commit in wc 1
  expected_output = svntest.wc.State(wc_dir, {
      'A/C' : Item(verb='Sending'),
      'A/I' : Item(verb='Deleting'),
      'A/B/F' : Item(verb='Deleting'),
      'A/D/H/omega' : Item(verb='Sending'),
      'A/B/E/alpha' : Item(verb='Sending'),
      'A/D/H/chi' : Item(verb='Deleting'),
      'A/B/E/beta' : Item(verb='Deleting'),
      'A/D/H/psi' : Item(verb='Deleting'),
      })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.tweak('A/C', 'A/D/H/omega', 'A/B/E/alpha', wc_rev=3,
                        status='  ')
  expected_status.remove('A/B/F', 'A/D/H/chi', 'A/B/E/beta', 'A/D/H/psi')
  commit = svntest.actions.run_and_verify_commit
  commit(wc_dir, expected_output, expected_status, None, wc_dir)

  # Edits in wc backup
  I_path = os.path.join(wc_backup, 'A', 'I')
  chi_path = os.path.join(wc_backup, 'A', 'D', 'H', 'chi')
  beta_path = os.path.join(wc_backup, 'A', 'B', 'E','beta')
  svntest.main.run_svn(None, 'propset', 'fooprop', 'foopropval', I_path)
  svntest.main.file_append(chi_path, 'appended chi text')
  svntest.main.run_svn(None, 'propset', 'fooprop', 'foopropval', beta_path)

  # Deletions in wc backup
  C_path = os.path.join(wc_backup, 'A', 'C')
  F_path = os.path.join(wc_backup, 'A', 'B', 'F')
  omega_path = os.path.join(wc_backup, 'A', 'D', 'H', 'omega')
  alpha_path = os.path.join(wc_backup, 'A', 'B', 'E', 'alpha')
  psi_path = os.path.join(wc_backup, 'A', 'D', 'H', 'psi')
  svntest.main.run_svn(None, 'rm', C_path, F_path, omega_path, alpha_path,
                       psi_path)

  # A commit of any one of these files or dirs should fail, preferably
  # with an out-of-date error message.
  error_re = "(out of date|not found)"
  commit(wc_backup, None, None, error_re, C_path)
  commit(wc_backup, None, None, error_re, I_path)
  commit(wc_backup, None, None, error_re, F_path)
  commit(wc_backup, None, None, error_re, omega_path)
  commit(wc_backup, None, None, error_re, alpha_path)
  commit(wc_backup, None, None, error_re, chi_path)
  commit(wc_backup, None, None, error_re, beta_path)
  commit(wc_backup, None, None, error_re, psi_path)

def commit_with_bad_log_message(sbox):
  "commit with a log message containing bad data"

  sbox.build()
  wc_dir = sbox.wc_dir

  iota_path = os.path.join(wc_dir, 'iota')
  log_msg_path = os.path.join(wc_dir, 'log-message')

  # Make a random change, so there's something to commit.
  svntest.main.file_append(iota_path, 'fish')

  # Create a log message containing a zero-byte.
  svntest.main.file_append(log_msg_path, '\x00')

  # Commit and expect an error.
  svntest.actions.run_and_verify_commit(wc_dir,
                                        None, None,
                                        "contains a zero byte",
                                        '-F', log_msg_path,
                                        iota_path)

def commit_with_mixed_line_endings(sbox):
  "commit with log message with mixed EOL"

  sbox.build()
  wc_dir = sbox.wc_dir

  expected_status = make_standard_slew_of_changes(wc_dir)

  iota_path = os.path.join(wc_dir, 'iota')
  log_msg_path = os.path.join(wc_dir, 'log-message')

  # Make a random change, so there's something to commit.
  svntest.main.file_append(iota_path, 'kebab')

  # Create a log message containing a zero-byte.
  svntest.main.file_append(log_msg_path, "test\nthis\n\rcase\r\n--This line, and those below, will be ignored--\n")

  # Commit and expect an error.
  svntest.actions.run_and_verify_commit(wc_dir,
                                        None, None,
                                        "Error normalizing log message to internal format",
                                        '-F', log_msg_path,
                                        iota_path)

def commit_with_mixed_line_endings_in_ignored_part(sbox):
  "commit with log message with mixed EOL in tail"

  sbox.build()
  wc_dir = sbox.wc_dir

  expected_status = make_standard_slew_of_changes(wc_dir)

  iota_path = os.path.join(wc_dir, 'iota')
  log_msg_path = os.path.join(wc_dir, 'log-message')

  # Make a random change, so there's something to commit.
  svntest.main.file_append(iota_path, 'cheeseburger')

  # Create a log message containing a zero-byte.
  svntest.main.file_append(log_msg_path, "test\n--This line, and those below, will be ignored--\nfoo\r\nbar\nbaz\n\r")

  # Create expected state.
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(verb='Sending'),
    })
  expected_status.tweak('iota', wc_rev=2, status='  ')

  # Commit the one file.
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        iota_path)

def from_wc_top_with_bad_editor(sbox):
  "commit with invalid external editor cmd"

  # Shortly after revision 5407, Vladimir Prus posted this bug recipe:
  #
  #   #!/bin/bash
  #   cd /tmp
  #   rm -rf repo wc
  #   svnadmin create repo
  #   svn mkdir file:///tmp/repo/foo -m ""
  #   svn co file:///tmp/repo/foo wc
  #   cd wc
  #   svn ps svn:externals "lib http://something.org/lib" .
  #   svn ci
  #
  # The final 'svn ci' would seg fault because of a problem in
  # calculating the paths to insert in the initial log message that
  # gets passed to the editor.
  #
  # So this regression test is primarily about making sure the seg
  # fault is gone, and only secondarily about testing that we get the
  # expected error from passing a bad editor cmd to Subversion.

  sbox.build()
  wc_dir = sbox.wc_dir

  svntest.actions.run_and_verify_svn("Unexpected failure from propset.",
                                     svntest.verify.AnyOutput, [],
                                     'pset', 'fish', 'food', wc_dir)
  os.chdir(wc_dir)
  exit_code, out, err = svntest.actions.run_and_verify_svn(
    "Commit succeeded when should have failed.",
    None, svntest.verify.AnyOutput,
    'ci', '--editor-cmd', 'no_such-editor')

  err = " ".join([x.strip() for x in err])
  if not (re.match(".*no_such-editor.*", err)
          and re.match(".*Commit failed.*", err)):
    print("Commit failed, but not in the way expected.")
    raise svntest.Failure


def mods_in_schedule_delete(sbox):
  "commit with mods in schedule delete"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Schedule a delete, then put in local mods
  C_path = os.path.join(wc_dir, 'A', 'C')
  svntest.actions.run_and_verify_svn(None, svntest.verify.AnyOutput, [],
                                     'rm', C_path)

  if not os.path.exists(C_path):
    os.mkdir(C_path)
  foo_path = os.path.join(C_path, 'foo')
  foo_contents = 'zig\nzag\n'
  svntest.main.file_append(foo_path, foo_contents)

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

  # Unversioned file still exists
  actual_contents = open(foo_path).read()
  if actual_contents != foo_contents:
    raise svntest.Failure


#----------------------------------------------------------------------
@Skip(is_non_posix_os_or_cygwin_platform)
@Issue(1954)
def tab_test(sbox):
  "tabs in paths"
  # For issue #1954.

  sbox.build()
  wc_dir = sbox.wc_dir

  tab_file = os.path.join(wc_dir, 'A', "tab\tfile")
  tab_dir  = os.path.join(wc_dir, 'A', "tab\tdir")
  source_url = sbox.repo_url + "/source_dir"
  tab_url = sbox.repo_url + "/tab%09dir"

  svntest.main.file_append(tab_file, "This file has a tab in it.")
  os.mkdir(tab_dir)

  def match_bad_tab_path(path, errlines):
    match_re = ".*: Invalid control character '0x09' in path .*"
    for line in errlines:
      if re.match (match_re, line):
        break
    else:
      raise svntest.Failure("Failed to find match_re in " + str(errlines))

  # add file to wc
  exit_code, outlines, errlines = svntest.main.run_svn(1, 'add', tab_file)
  match_bad_tab_path(tab_file, errlines)

  # add dir to wc
  exit_code, outlines, errlines = svntest.main.run_svn(1, 'add', tab_dir)
  match_bad_tab_path(tab_dir, errlines)

  # mkdir URL
  exit_code, outlines, errlines = svntest.main.run_svn(1, 'mkdir',
                                                       '-m', 'msg', tab_url)
  match_bad_tab_path(tab_dir, errlines)

  # copy URL
  svntest.main.run_svn(1,
                       'mkdir', '-m', 'msg', source_url)
  exit_code, outlines, errlines = svntest.main.run_svn(1, 'copy',
                                                       '-m', 'msg',
                                                       source_url, tab_url)
  match_bad_tab_path(tab_dir, errlines)

  # mv URL
  exit_code, outlines, errlines = svntest.main.run_svn(1, 'mv', '-m', 'msg',
                                                       source_url, tab_url)
  match_bad_tab_path(tab_dir, errlines)

#----------------------------------------------------------------------
@Issue(2285)
def local_mods_are_not_commits(sbox):
  "local ops should not be treated like commits"

  # For issue #2285.
  #
  # Some commands can run on either a URL or a local path.  These
  # commands take a log message, intended for the URL case.
  # Therefore, they should make sure that getting a log message for
  # a local operation errors (because not committing).
  #
  # This is in commit_tests.py because the unifying theme is that
  # commits are *not* happening.  And because there was no better
  # place to put it :-).

  sbox.build()
  wc_dir = sbox.wc_dir
  expected_error = '.*Local, non-commit operations do not take a log message.*'

  # copy wc->wc
  svntest.actions.run_and_verify_svn(None, None, expected_error,
                                     'cp', '-m', 'log msg',
                                     os.path.join(wc_dir, 'iota'),
                                     os.path.join(wc_dir, 'iota2'))

  # copy repos->wc
  svntest.actions.run_and_verify_svn(None, None, expected_error,
                                     'cp', '-m', 'log msg',
                                     sbox.repo_url + "/iota",
                                     os.path.join(wc_dir, 'iota2'))

  # delete
  svntest.actions.run_and_verify_svn(None, None, expected_error,
                                     'rm', '-m', 'log msg',
                                     os.path.join(wc_dir, 'A', 'D', 'gamma'))

  # mkdir
  svntest.actions.run_and_verify_svn(None, None, expected_error,
                                     'mkdir', '-m', 'log msg',
                                     os.path.join(wc_dir, 'newdir'))

  # rename
  svntest.actions.run_and_verify_svn(None, None, expected_error,
                                     'cp', '-m', 'log msg',
                                     os.path.join(wc_dir, 'A', 'mu'),
                                     os.path.join(wc_dir, 'A', 'yu'))


#----------------------------------------------------------------------
# Test if the post-commit error message is returned back to the svn
# client and is displayed as a warning.
@Issue(3553)
def post_commit_hook_test(sbox):
  "post commit hook failure case testing"

  sbox.build()

  # Get paths to the working copy and repository
  wc_dir = sbox.wc_dir
  repo_dir = sbox.repo_dir

  # Create a hook that outputs a message to stderr and returns exit code 1
  # Include a non-XML-safe message to regression-test issue #3553.
  error_msg = "Text with <angle brackets> & ampersand"
  svntest.actions.create_failing_hook(repo_dir, "post-commit", error_msg)

  # Modify iota just so there is something to commit.
  iota_path = os.path.join(wc_dir, "iota")
  svntest.main.file_append(iota_path, "lakalakalakalaka")

  # Now, commit and examine the output (we happen to know that the
  # filesystem will report an absolute path because that's the way the
  # filesystem is created by this test suite.
  expected_output = [ "Sending        "+ iota_path + "\n",
                      "Transmitting file data .\n",
                      "Committed revision 2.\n",
                      "\n",
                      "Warning: " +
                        svntest.actions.hook_failure_message('post-commit'),
                      error_msg + "\n",
                    ]

  svntest.actions.run_and_verify_svn(None, expected_output, [],
                                     'ci', '-m', 'log msg', iota_path)

#----------------------------------------------------------------------
# Commit two targets non-recursively, but both targets should be the
# same folder (in multiple variations). Test that svn handles this correctly.
def commit_same_folder_in_targets(sbox):
  "commit two targets, both the same folder"

  sbox.build()
  wc_dir = sbox.wc_dir

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

  svntest.main.file_append(iota_path, "added extra line to file iota")

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

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

  # Commit the wc_dir and iota.
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        '-N',
                                        wc_dir,
                                        iota_path)

#----------------------------------------------------------------------
# test for issue 2459: verify that commit fails when a file with mixed
# eol-styles is included, and show an error message which includes the
# filename.
@Issue(2459)
def commit_inconsistent_eol(sbox):
  "commit files with inconsistent eol should fail"

  sbox.build()
  wc_dir = sbox.wc_dir

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

  svntest.main.run_svn(None, 'propset', 'svn:eol-style', 'native', iota_path)
  svntest.main.file_append_binary(iota_path,
                                  "added extra line to file iota\012"
                                  "added extra line to file iota\015")
  svntest.main.file_append(mu_path, "added extra line to file mu\n"
                                    "added extra line to file mu\n")

  expected_err = ".*iota.*"

  svntest.actions.run_and_verify_svn(None, None, expected_err,
                                     'commit', '-m', 'log message',
                                     wc_dir)


@SkipUnless(server_has_revprop_commit)
def mkdir_with_revprop(sbox):
  "set revision props during remote mkdir"

  sbox.build()
  remote_dir = sbox.repo_url + "/dir"

  svntest.actions.run_and_verify_svn(None, None, [], 'mkdir', '-m', 'msg',
                                     '--with-revprop', 'bug=42', remote_dir)

  expected = svntest.verify.UnorderedOutput(
                  ['Unversioned properties on revision 2:\n',
                   '  svn:author\n','  svn:date\n',  '  svn:log\n',
                   '  bug\n'])
  svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
                                     '--revprop', '-r', 2, sbox.repo_url)
  svntest.actions.run_and_verify_svn(None, '42', [], 'propget', 'bug',
                                     '--revprop', '-r', 2, sbox.repo_url)


@SkipUnless(server_has_revprop_commit)
def delete_with_revprop(sbox):
  "set revision props during remote delete"

  sbox.build()
  remote_dir = sbox.repo_url + "/dir"
  svntest.actions.run_and_verify_svn(None, None, [], 'mkdir', '-m', 'msg',
                                     remote_dir)

  svntest.actions.run_and_verify_svn(None, None, [], 'delete', '-m', 'msg',
                                     '--with-revprop', 'bug=52', remote_dir)

  expected = svntest.verify.UnorderedOutput(
                  ['Unversioned properties on revision 3:\n',
                   '  svn:author\n','  svn:date\n',  '  svn:log\n',
                   '  bug\n'])
  svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
                                     '--revprop', '-r', 3, sbox.repo_url)
  svntest.actions.run_and_verify_svn(None, '52', [], 'propget', 'bug',
                                     '--revprop', '-r', 3, sbox.repo_url)


@SkipUnless(server_has_revprop_commit)
def commit_with_revprop(sbox):
  "set revision props during commit"

  sbox.build()
  wc_dir = sbox.wc_dir
  expected_status = make_standard_slew_of_changes(wc_dir)

  omega_path = os.path.join(wc_dir, 'A', 'D', 'H', 'omega')
  gloo_path = os.path.join(wc_dir, 'A', 'D', 'H', 'gloo')
  expected_output = svntest.wc.State(wc_dir, {
    'A/D/H/omega' : Item(verb='Sending'),
    'A/D/H/gloo' : Item(verb='Adding'),
    })

  expected_status.tweak('A/D/H/omega', wc_rev=2, status='  ')
  expected_status.tweak('A/D/H/gloo', wc_rev=2, status='  ')

  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        '-m', 'msg',
                                        '--with-revprop', 'bug=62',
                                        omega_path, gloo_path)

  expected = svntest.verify.UnorderedOutput(
                  ['Unversioned properties on revision 2:\n',
                   '  svn:author\n','  svn:date\n',  '  svn:log\n',
                   '  bug\n'])
  svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
                                     '--revprop', '-r', 2, sbox.repo_url)
  svntest.actions.run_and_verify_svn(None, '62', [], 'propget', 'bug',
                                     '--revprop', '-r', 2, sbox.repo_url)


@SkipUnless(server_has_revprop_commit)
def import_with_revprop(sbox):
  "set revision props during import"

  sbox.build()
  local_dir = os.path.join(sbox.wc_dir, 'folder')
  local_file = os.path.join(sbox.wc_dir, 'folder', 'file')
  os.mkdir(local_dir)
  svntest.main.file_write(local_file, "xxxx")

  svntest.actions.run_and_verify_svn(None, None, [], 'import', '-m', 'msg',
                                     '--with-revprop', 'bug=72', local_dir,
                                     sbox.repo_url)

  expected = svntest.verify.UnorderedOutput(
                  ['Unversioned properties on revision 2:\n',
                   '  svn:author\n','  svn:date\n',  '  svn:log\n',
                   '  bug\n'])
  svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
                                     '--revprop', '-r', 2, sbox.repo_url)
  svntest.actions.run_and_verify_svn(None, '72', [], 'propget', 'bug',
                                     '--revprop', '-r', 2, sbox.repo_url)


@SkipUnless(server_has_revprop_commit)
def copy_R2R_with_revprop(sbox):
  "set revision props during repos-to-repos copy"

  sbox.build()
  remote_dir1 = sbox.repo_url + "/dir1"
  remote_dir2 = sbox.repo_url + "/dir2"
  svntest.actions.run_and_verify_svn(None, None, [], 'mkdir', '-m', 'msg',
                                     remote_dir1)

  svntest.actions.run_and_verify_svn(None, None, [], 'copy', '-m', 'msg',
                                     '--with-revprop', 'bug=82', remote_dir1,
                                     remote_dir2)

  expected = svntest.verify.UnorderedOutput(
                  ['Unversioned properties on revision 3:\n',
                   '  svn:author\n','  svn:date\n',  '  svn:log\n',
                   '  bug\n'])
  svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
                                     '--revprop', '-r', 3, sbox.repo_url)
  svntest.actions.run_and_verify_svn(None, '82', [], 'propget', 'bug',
                                     '--revprop', '-r', 3, sbox.repo_url)


@SkipUnless(server_has_revprop_commit)
def copy_WC2R_with_revprop(sbox):
  "set revision props during wc-to-repos copy"

  sbox.build()
  remote_dir = sbox.repo_url + "/dir"
  local_dir = os.path.join(sbox.wc_dir, 'folder')
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'mkdir', local_dir)

  svntest.actions.run_and_verify_svn(None, None, [], 'copy', '-m', 'msg',
                                     '--with-revprop', 'bug=92', local_dir,
                                     remote_dir)

  expected = svntest.verify.UnorderedOutput(
                  ['Unversioned properties on revision 2:\n',
                   '  svn:author\n','  svn:date\n',  '  svn:log\n',
                   '  bug\n'])
  svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
                                     '--revprop', '-r', 2, sbox.repo_url)
  svntest.actions.run_and_verify_svn(None, '92', [], 'propget', 'bug',
                                     '--revprop', '-r', 2, sbox.repo_url)


@SkipUnless(server_has_revprop_commit)
def move_R2R_with_revprop(sbox):
  "set revision props during repos-to-repos move"

  sbox.build()
  remote_dir1 = sbox.repo_url + "/dir1"
  remote_dir2 = sbox.repo_url + "/dir2"
  svntest.actions.run_and_verify_svn(None, None, [], 'mkdir', '-m', 'msg',
                                     remote_dir1)

  svntest.actions.run_and_verify_svn(None, None, [], 'move', '-m', 'msg',
                                     '--with-revprop', 'bug=102', remote_dir1,
                                     remote_dir2)

  expected = svntest.verify.UnorderedOutput(
                  ['Unversioned properties on revision 3:\n',
                   '  svn:author\n','  svn:date\n',  '  svn:log\n',
                   '  bug\n'])
  svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
                                     '--revprop', '-r', 3, sbox.repo_url)
  svntest.actions.run_and_verify_svn(None, '102', [], 'propget', 'bug',
                                     '--revprop', '-r', 3, sbox.repo_url)


@SkipUnless(server_has_revprop_commit)
def propedit_with_revprop(sbox):
  "set revision props during remote property edit"

  sbox.build()
  svntest.main.use_editor('append_foo')

  svntest.actions.run_and_verify_svn(None, None, [], 'propedit', '-m', 'msg',
                                     '--with-revprop', 'bug=112', 'prop',
                                     sbox.repo_url)

  expected = svntest.verify.UnorderedOutput(
                  ['Unversioned properties on revision 2:\n',
                   '  svn:author\n','  svn:date\n',  '  svn:log\n',
                   '  bug\n'])
  svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
                                     '--revprop', '-r', 2, sbox.repo_url)
  svntest.actions.run_and_verify_svn(None, '112', [], 'propget', 'bug',
                                     '--revprop', '-r', 2, sbox.repo_url)


@SkipUnless(server_has_revprop_commit)
def set_multiple_props_with_revprop(sbox):
  "set multiple revision props during remote mkdir"

  sbox.build()
  remote_dir = sbox.repo_url + "/dir"

  svntest.actions.run_and_verify_svn(None, None, [], 'mkdir', '-m', 'msg',
                                     '--with-revprop', 'bug=32',
                                     '--with-revprop', 'ref=22', remote_dir)

  expected = svntest.verify.UnorderedOutput(
                  ['Unversioned properties on revision 2:\n',
                   '  svn:author\n','  svn:date\n',  '  svn:log\n',
                   '  bug\n', '  ref\n'])
  svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
                                     '--revprop', '-r', 2, sbox.repo_url)
  svntest.actions.run_and_verify_svn(None, '32', [], 'propget', 'bug',
                                     '--revprop', '-r', 2, sbox.repo_url)
  svntest.actions.run_and_verify_svn(None, '22', [], 'propget', 'ref',
                                     '--revprop', '-r', 2, sbox.repo_url)


@SkipUnless(server_has_revprop_commit)
def use_empty_value_in_revprop_pair(sbox):
  "set revprop without value ('') during remote mkdir"

  sbox.build()
  remote_dir = sbox.repo_url + "/dir"

  svntest.actions.run_and_verify_svn(None, None, [], 'mkdir', '-m', 'msg',
                                     '--with-revprop', 'bug=',
                                     '--with-revprop', 'ref=', remote_dir)

  expected = svntest.verify.UnorderedOutput(
                  ['Unversioned properties on revision 2:\n',
                   '  svn:author\n','  svn:date\n',  '  svn:log\n',
                   '  bug\n', '  ref\n'])
  svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
                                     '--revprop', '-r', 2, sbox.repo_url)
  svntest.actions.run_and_verify_svn(None, '', [], 'propget', 'bug',
                                     '--revprop', '-r', 2, sbox.repo_url)
  svntest.actions.run_and_verify_svn(None, '', [], 'propget', 'ref',
                                     '--revprop', '-r', 2, sbox.repo_url)


@SkipUnless(server_has_revprop_commit)
def no_equals_in_revprop_pair(sbox):
  "set revprop without '=' during remote mkdir"

  sbox.build()
  remote_dir = sbox.repo_url + "/dir"
  svntest.actions.run_and_verify_svn(None, None, [], 'mkdir', '-m', 'msg',
                                     '--with-revprop', 'bug',
                                     '--with-revprop', 'ref', remote_dir)

  expected = svntest.verify.UnorderedOutput(
                  ['Unversioned properties on revision 2:\n',
                   '  svn:author\n','  svn:date\n',  '  svn:log\n',
                   '  bug\n', '  ref\n'])
  svntest.actions.run_and_verify_svn(None, expected, [], 'proplist',
                                     '--revprop', '-r', 2, sbox.repo_url)
  svntest.actions.run_and_verify_svn(None, '', [], 'propget', 'bug',
                                     '--revprop', '-r', 2, sbox.repo_url)
  svntest.actions.run_and_verify_svn(None, '', [], 'propget', 'ref',
                                     '--revprop', '-r', 2, sbox.repo_url)


@SkipUnless(server_has_revprop_commit)
def set_invalid_revprops(sbox):
  "set invalid revision props during remote mkdir"

  sbox.build()
  remote_dir = sbox.repo_url + "/dir"
  # Try to set svn: revprops.
  expected = '.*Standard properties can\'t.*'
  svntest.actions.run_and_verify_svn(None, [], expected, 'mkdir', '-m', 'msg',
                                     '--with-revprop', 'svn:author=42', remote_dir)
  svntest.actions.run_and_verify_svn(None, [], expected, 'mkdir', '-m', 'msg',
                                     '--with-revprop', 'svn:log=42', remote_dir)
  svntest.actions.run_and_verify_svn(None, [], expected, 'mkdir', '-m', 'msg',
                                     '--with-revprop', 'svn:date=42', remote_dir)
  svntest.actions.run_and_verify_svn(None, [], expected, 'mkdir', '-m', 'msg',
                                     '--with-revprop', 'svn:foo=bar', remote_dir)

  # Empty revprop pair.
  svntest.actions.run_and_verify_svn(None, [],
                                     'svn: E205000: '
                                     'Revision property pair is empty',
                                     'mkdir', '-m', 'msg',
                                     '--with-revprop', '',
                                     remote_dir)

#----------------------------------------------------------------------
@Issue(3553)
def start_commit_hook_test(sbox):
  "start-commit hook failure case testing"

  sbox.build()

  # Get paths to the working copy and repository
  wc_dir = sbox.wc_dir
  repo_dir = sbox.repo_dir

  # Create a hook that outputs a message to stderr and returns exit code 1
  # Include a non-XML-safe message to regression-test issue #3553.
  error_msg = "Text with <angle brackets> & ampersand"
  svntest.actions.create_failing_hook(repo_dir, "start-commit", error_msg)

  # Modify iota just so there is something to commit.
  iota_path = os.path.join(wc_dir, "iota")
  svntest.main.file_append(iota_path, "More stuff in iota")

  # Commit, expect error code 1
  exit_code, actual_stdout, actual_stderr = svntest.main.run_svn(
    1, 'ci', '--quiet', '-m', 'log msg', wc_dir)

  # No stdout expected
  svntest.verify.compare_and_display_lines('Start-commit hook test',
                                           'STDOUT', [], actual_stdout)

  # Compare only the last two lines of stderr since the preceding ones
  # contain source code file and line numbers.
  if len(actual_stderr) > 2:
    actual_stderr = actual_stderr[-2:]
  expected_stderr = [ "svn: E165001: " +
                        svntest.actions.hook_failure_message('start-commit'),
                      error_msg + "\n",
                    ]
  svntest.verify.compare_and_display_lines('Start-commit hook test',
                                           'STDERR',
                                           expected_stderr, actual_stderr)

#----------------------------------------------------------------------
@Issue(3553)
def pre_commit_hook_test(sbox):
  "pre-commit hook failure case testing"

  sbox.build()

  # Get paths to the working copy and repository
  wc_dir = sbox.wc_dir
  repo_dir = sbox.repo_dir

  # Create a hook that outputs a message to stderr and returns exit code 1
  # Include a non-XML-safe message to regression-test issue #3553.
  error_msg = "Text with <angle brackets> & ampersand"
  svntest.actions.create_failing_hook(repo_dir, "pre-commit", error_msg)

  # Modify iota just so there is something to commit.
  iota_path = os.path.join(wc_dir, "iota")
  svntest.main.file_append(iota_path, "More stuff in iota")

  # Commit, expect error code 1
  exit_code, actual_stdout, actual_stderr = svntest.main.run_svn(
    1, 'ci', '--quiet', '-m', 'log msg', wc_dir)

  # No stdout expected
  svntest.verify.compare_and_display_lines('Pre-commit hook test',
                                           'STDOUT', [], actual_stdout)

  # Compare only the last two lines of stderr since the preceding ones
  # contain source code file and line numbers.
  if len(actual_stderr) > 2:
    actual_stderr = actual_stderr[-2:]
  expected_stderr = [ "svn: E165001: " +
                        svntest.actions.hook_failure_message('pre-commit'),
                      error_msg + "\n",
                    ]
  svntest.verify.compare_and_display_lines('Pre-commit hook test',
                                           'STDERR',
                                           expected_stderr, actual_stderr)

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

def versioned_log_message(sbox):
  "'svn commit -F foo' when foo is a versioned file"

  sbox.build()

  os.chdir(sbox.wc_dir)

  iota_path = os.path.join('iota')
  mu_path = os.path.join('A', 'mu')
  log_path = os.path.join('A', 'D', 'H', 'omega')

  svntest.main.file_append(iota_path, "2")

  # try to check in a change using a versioned file as your log entry.
  svntest.actions.run_and_verify_svn(None, None, svntest.verify.AnyOutput,
                                     'ci', '-F', log_path)

  # force it.  should not produce any errors.
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'ci', '-F', log_path, '--force-log')

  svntest.main.file_append(mu_path, "2")

  # try the same thing, but specifying the file to commit explicitly.
  svntest.actions.run_and_verify_svn(None, None, svntest.verify.AnyOutput,
                                     'ci', '-F', log_path, mu_path)

  # force it...  should succeed.
  svntest.actions.run_and_verify_svn(None, None, [],
                                     'ci',
                                     '-F', log_path,
                                     '--force-log', mu_path)

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

def changelist_near_conflict(sbox):
  "'svn commit --changelist=foo' above a conflict"

  sbox.build()

  wc_dir = sbox.wc_dir
  iota_path = os.path.join(wc_dir, "iota")
  mu_path = os.path.join(wc_dir, "A", "mu")
  gloo_path = os.path.join(wc_dir, "A", "D", "H", "gloo")

  expected_status = make_standard_slew_of_changes(wc_dir)

  # Create a changelist.
  changelist_name = "logical-changeset"
  svntest.actions.run_and_verify_svn(None, None, [],
                                     "changelist", changelist_name,
                                     mu_path, gloo_path)

  # Create a conflict (making r2 in the process).
  inject_conflict_into_wc(sbox, 'iota', iota_path,
                          None, expected_status, 2)

  # Commit the changelist.
  expected_output = svntest.wc.State(wc_dir, {
    "A/D/H/gloo" : Item(verb='Adding'),
    })
  expected_status.tweak("A/D/H/gloo", wc_rev=3, status="  ")
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        "--changelist=" + changelist_name,
                                        "-m", "msg", wc_dir)


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

def commit_out_of_date_file(sbox):
  "try to commit a file that is out-of-date"

  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)

  pi_path = os.path.join(wc_dir, 'A', 'D', 'G', 'pi')
  backup_pi_path = os.path.join(wc_backup, 'A', 'D', 'G', 'pi')

  svntest.main.file_append(pi_path, "new line\n")
  expected_output = svntest.wc.State(wc_dir, {
    "A/D/G/pi" : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak("A/D/G/pi", wc_rev=2, status="  ")
  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        "-m", "log message", wc_dir)

  svntest.main.file_append(backup_pi_path, "hello")
  expected_err = ".*(pi.*out of date|Out of date.*pi).*"
  svntest.actions.run_and_verify_svn(None, None, expected_err,
                                     'commit', '-m', 'log message',
                                     wc_backup)

@SkipUnless(server_gets_client_capabilities)
@Issue(2991)
def start_commit_detect_capabilities(sbox):
  "start-commit hook sees client capabilities"  # Issue #2991
  sbox.build()
  wc_dir = sbox.wc_dir
  repos_dir = sbox.repo_dir

  # Create a start-commit hook that detects the "mergeinfo" capability.
  hook_text = "import sys\n"                                 + \
              "fp = open(sys.argv[1] + '/hooks.log', 'w')\n" + \
              "caps = sys.argv[3].split(':')\n"              + \
              "if 'mergeinfo' in caps:\n"                    + \
              "  fp.write('yes')\n"                          + \
              "else:\n"                                      + \
              "  fp.write('no')\n"                           + \
              "fp.close()\n"

  start_commit_hook = svntest.main.get_start_commit_hook_path(repos_dir)
  svntest.main.create_python_hook_script(start_commit_hook, hook_text)

  # Commit something.
  iota_path = os.path.join(wc_dir, "iota")
  svntest.main.file_append(iota_path, "More stuff in iota")
  svntest.actions.run_and_verify_svn(None, [], [], 'ci', '--quiet',
                                     '-m', 'log msg', wc_dir)

  # Check that "mergeinfo" was detected.
  log_path = os.path.join(repos_dir, "hooks.log")
  if os.path.exists(log_path):
    data = open(log_path).read()
    os.unlink(log_path)
  else:
    raise svntest.verify.SVNUnexpectedOutput("'%s' not found") % log_path
  if data != 'yes':
    raise svntest.Failure

# Test for issue #3198
@Issue(3198)
def commit_added_missing(sbox):
  "commit a missing to-be-added file should fail"

  sbox.build()
  wc_dir = sbox.wc_dir
  mu_path = os.path.join(wc_dir, 'A', 'mu')
  a_path = os.path.join(wc_dir, 'A', 'a.txt')
  b_path = os.path.join(wc_dir, 'A', 'b.txt')

  # Make two copies of mu: a and b
  svntest.main.run_svn(None, 'cp', mu_path, a_path)
  svntest.main.run_svn(None, 'cp', mu_path, b_path)

  # remove b, make it missing
  os.remove(b_path)

  # Commit, hoping to see an error
  svntest.actions.run_and_verify_svn("Commit should have failed",
                                     [], ".* is scheduled for addition, but is missing",
                                     'commit', '-m', 'logmsg', wc_dir)

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

# Helper for commit-failure tests
def commit_fails_at_path(path, wc_dir, error_re):
  svntest.actions.run_and_verify_commit(wc_dir,
                                        None,
                                        None,
                                        error_re,
                                        path)

def tree_conflicts_block_commit(sbox):
  "tree conflicts block commit"

  # Commit is not allowed in a directory containing tree conflicts.
  # This test corresponds to use cases 1-3 (with file victims) in
  # notes/tree-conflicts/use-cases.txt.

  svntest.actions.build_greek_tree_conflicts(sbox)
  wc_dir = sbox.wc_dir
  A = os.path.join(wc_dir, 'A')
  D = os.path.join(wc_dir, 'A', 'D')
  G = os.path.join(wc_dir, 'A', 'D', 'G')

  error_re = "remains in conflict"
  commit_fails_at_path(wc_dir, wc_dir, error_re)
  commit_fails_at_path(A, A, error_re)
  commit_fails_at_path(D, D, error_re)
  commit_fails_at_path(G, G, error_re)
  commit_fails_at_path(os.path.join(G, 'pi'), G, error_re)


def tree_conflicts_resolved(sbox):
  "tree conflicts resolved"

  # Commit is allowed after tree conflicts are resolved.
  # This test corresponds to use cases 1-3 in
  # notes/tree-conflicts/use-cases.txt.

  svntest.actions.build_greek_tree_conflicts(sbox)
  wc_dir = sbox.wc_dir

  # Duplicate wc for tests
  wc_dir_2 = sbox.add_wc_path('2')
  svntest.actions.duplicate_dir(wc_dir, wc_dir_2)

  # Mark the tree conflict victims as resolved
  G = os.path.join(wc_dir, 'A', 'D', 'G')
  victims = [ os.path.join(G, v) for v in ['pi', 'rho', 'tau'] ]
  svntest.actions.run_and_verify_resolved(victims)

  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.tweak('A/D/G/pi',  status='D ')
  expected_status.tweak('A/D/G/rho', status='A ', copied='+', wc_rev='-')
  expected_status.remove('A/D/G/tau')

  svntest.actions.run_and_verify_status(wc_dir, expected_status)

  # Recursively resolved in parent directory -- expect same result
  G2 = os.path.join(wc_dir_2, 'A', 'D', 'G')
  victims = [ os.path.join(G2, v) for v in ['pi', 'rho', 'tau'] ]
  svntest.actions.run_and_verify_resolved(victims, G2, '-R')

  expected_status.wc_dir = wc_dir_2
  svntest.actions.run_and_verify_status(wc_dir_2, expected_status)

#----------------------------------------------------------------------
def commit_multiple_nested_deletes(sbox):
  "committing multiple nested deletes"

  sbox.build()
  wc_dir = sbox.wc_dir

  A = os.path.join(wc_dir, 'A')
  A_B = os.path.join(A, 'B')

  sbox.simple_rm('A')

  svntest.main.run_svn(None, 'ci', A, A_B, '-m', 'Q')

@Issue(4042)
def commit_incomplete(sbox):
  "commit an incomplete dir"

  sbox.build()
  wc_dir = sbox.wc_dir

  sbox.simple_propset('pname', 'pval', 'A/B')
  svntest.actions.set_incomplete(sbox.ospath('A/B'), 1)

  expected_output = svntest.wc.State(wc_dir, {
      'A/B' : Item(verb='Sending'),
      })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/B',  status='! ', wc_rev=2)

  svntest.actions.run_and_verify_commit(wc_dir,
                                        expected_output,
                                        expected_status,
                                        None,
                                        wc_dir)
  
#----------------------------------------------------------------------
# Reported here:
#   Message-ID: <4EBF0FC9.300@gmail.com>
#   Date: Sun, 13 Nov 2011 13:31:05 +1300
#   From: Fergus Slorach <sugref@gmail.com>
#   Subject: svn commit --targets behaviour change in 1.7?
def commit_add_subadd(sbox):
  "committing add with explicit subadd targets"

  sbox.build()
  wc_dir = sbox.wc_dir

  targets_file = sbox.ospath('targets') # ### better tempdir?
  targets_file = os.path.abspath(targets_file)

  # prepare targets file
  targets = "A/D A/D/H A/D/H/chi A/D/H/omega A/D/H/psi".split()
  open(targets_file, 'w').write("\n".join(targets))

  # r2: rm A/D
  sbox.simple_rm('A/D')
  sbox.simple_commit(message='rm')

  # r3: revert r2, with specific invocation
  os.chdir(wc_dir)
  svntest.main.run_svn(None, 'up')
  svntest.main.run_svn(None, 'merge', '-c', '-2', './')
  svntest.main.run_svn(None, 'commit', '--targets', targets_file, '-mm')


@Issue(4480)
def commit_mergeinfo_ood(sbox):
  "commit of mergeinfo that should cause out of date"

  sbox.build()
  sbox.simple_rm('A', 'iota')
  sbox.simple_commit() # r2

  sbox.simple_mkdir('trunk', 'branch')
  sbox.simple_commit() # r3

  sbox.simple_append('trunk/a', 'This is a\n')
  sbox.simple_add('trunk/a')
  sbox.simple_commit() # r4

  sbox.simple_append('trunk/b', 'This is b\n')
  sbox.simple_add('trunk/b')
  sbox.simple_commit() # r5

  sbox.simple_update() # To r5

  expected_output = [
    '--- Merging r4 into \'%s\':\n' % sbox.ospath('branch'),
    'A    %s\n' % sbox.ospath('branch/a'),
    '--- Recording mergeinfo for merge of r4' \
                                ' into \'%s\':\n' % sbox.ospath('branch'),
    ' U   %s\n' % sbox.ospath('branch'),
  ]
  svntest.actions.run_and_verify_svn(None, expected_output, [],
                                     'merge', '-c4', '^/trunk',
                                     sbox.ospath('branch'))

  sbox.simple_commit()

  svntest.actions.run_and_verify_svn(None, None, [],
                                     'update', '-r5', sbox.wc_dir)

  expected_output = [
    '--- Merging r5 into \'%s\':\n' % sbox.ospath('branch'),
    'A    %s\n' % sbox.ospath('branch/b'),
    '--- Recording mergeinfo for merge of r5 into \'%s\':\n' % sbox.ospath('branch'),
    ' U   %s\n' % sbox.ospath('branch'),
  ]
  svntest.actions.run_and_verify_svn(None, expected_output, [],
                                     'merge', '-c5', '^/trunk',
                                     sbox.ospath('branch'))

  # Currently this commit succeeds with dav over HTTPv2, while it should really fail
  expected_err = '.*out of date.*'
  svntest.actions.run_and_verify_svn(None, None, expected_err,
                                     'commit', sbox.ospath(''), '-m', 'M')

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

# list all tests here, starting with None:
test_list = [ None,
              commit_one_file,
              commit_one_new_file,
              commit_one_new_binary_file,
              commit_multiple_targets,
              commit_multiple_targets_2,
              commit_inclusive_dir,
              commit_top_dir,
              commit_unversioned_thing,
              nested_dir_replacements,
              hudson_part_1,
              hudson_part_1_variation_1,
              hudson_part_1_variation_2,
              hudson_part_2,
              hudson_part_2_1,
              hook_test,
              merge_mixed_revisions,
              commit_uri_unsafe,
              commit_deleted_edited,
              commit_in_dir_scheduled_for_addition,
              commit_rmd_and_deleted_file,
              commit_add_file_twice,
              commit_from_long_dir,
              commit_with_lock,
              commit_current_dir,
              commit_multiple_wc_nested,
              commit_multiple_wc,
              commit_multiple_wc_multiple_repos,
              commit_nonrecursive,
              failed_commit,
              commit_out_of_date_deletions,
              commit_with_bad_log_message,
              commit_with_mixed_line_endings,
              commit_with_mixed_line_endings_in_ignored_part,
              from_wc_top_with_bad_editor,
              mods_in_schedule_delete,
              tab_test,
              local_mods_are_not_commits,
              post_commit_hook_test,
              commit_same_folder_in_targets,
              commit_inconsistent_eol,
              mkdir_with_revprop,
              delete_with_revprop,
              commit_with_revprop,
              import_with_revprop,
              copy_R2R_with_revprop,
              copy_WC2R_with_revprop,
              move_R2R_with_revprop,
              propedit_with_revprop,
              set_multiple_props_with_revprop,
              use_empty_value_in_revprop_pair,
              no_equals_in_revprop_pair,
              set_invalid_revprops,
              start_commit_hook_test,
              pre_commit_hook_test,
              versioned_log_message,
              changelist_near_conflict,
              commit_out_of_date_file,
              start_commit_detect_capabilities,
              commit_added_missing,
              tree_conflicts_block_commit,
              tree_conflicts_resolved,
              commit_multiple_nested_deletes,
              commit_incomplete,
              commit_add_subadd,
              commit_mergeinfo_ood,
             ]

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


### End of file.