#!/usr/bin/env python
#  -*- coding: utf-8 -*-
#
#  patch_tests.py:  some basic patch tests
#
#  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 base64
import os
import re
import sys
import tempfile
import textwrap
import zlib
import posixpath
import filecmp

# 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

########################################################################
#Tests

def patch(sbox):
  "basic patch"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  mu_path = sbox.ospath('A/mu')

  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "\n",
    "Again, we wish to congratulate you over your email success in our\n"
    "computer Balloting.\n"
  ]

  # Set mu contents
  svntest.main.file_write(mu_path, ''.join(mu_contents))
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu'       : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)

  # Apply patch

  unidiff_patch = [
    "Index: A/D/gamma\n",
    "===================================================================\n",
    "--- A/D/gamma\t(revision 1)\n",
    "+++ A/D/gamma\t(working copy)\n",
    "@@ -1 +1 @@\n",
    "-This is the file 'gamma'.\n",
    "+It is the file 'gamma'.\n",
    "Index: iota\n",
    "===================================================================\n",
    "--- iota\t(revision 1)\n",
    "+++ iota\t(working copy)\n",
    "@@ -1 +1,2 @@\n",
    " This is the file 'iota'.\n",
    "+Some more bytes\n",
    "\n",
    "Index: new\n",
    "===================================================================\n",
    "--- new	(revision 0)\n",
    "+++ new	(revision 0)\n",
    "@@ -0,0 +1 @@\n",
    "+new\n",
    "\n",
    "--- A/mu.orig	2009-06-24 15:23:55.000000000 +0100\n",
    "+++ A/mu	2009-06-24 15:21:23.000000000 +0100\n",
    "@@ -6,6 +6,9 @@\n",
    " through a computer ballot system drawn from over 100,000 company\n",
    " and 50,000,000 individual email addresses from all over the world.\n",
    " \n",
    "+It is a promotional program aimed at encouraging internet users;\n",
    "+therefore you do not need to buy ticket to enter for it.\n",
    "+\n",
    " Your email address drew and have won the sum of  750,000 Euros\n",
    " ( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    " file with\n",
    "@@ -14,11 +17,8 @@\n",
    "     BATCH NUMBERS :\n",
    "     EULO/1007/444/606/08;\n",
    "     SERIAL NUMBER: 45327\n",
    "-and PROMOTION DATE: 13th June. 2009\n",
    "+and PROMOTION DATE: 14th June. 2009\n",
    " \n",
    " To claim your winning prize, you are to contact the appointed\n",
    " agent below as soon as possible for the immediate release of your\n",
    " winnings with the below details.\n",
    "-\n",
    "-Again, we wish to congratulate you over your email success in our\n",
    "-computer Balloting.\n",
    "Index: A/B/E/beta\n",
    "===================================================================\n",
    "--- A/B/E/beta	(revision 1)\n",
    "+++ A/B/E/beta	(working copy)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'beta'.\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  gamma_contents = "It is the file 'gamma'.\n"
  iota_contents = "This is the file 'iota'.\nSome more bytes\n"
  new_contents = "new\n"
  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "It is a promotional program aimed at encouraging internet users;\n",
    "therefore you do not need to buy ticket to enter for it.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 14th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
  ]

  expected_output = wc.State(wc_dir, {
    'A/D/gamma'  : Item(status='U '),
    'iota'       : Item(status='U '),
    'new'        : Item(status='A '),
    'A/mu'       : Item(status='U '),
    'A/B/E/beta' : Item(status='D '),
  })

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/D/gamma', contents=gamma_contents)
  expected_disk.tweak('iota', contents=iota_contents)
  expected_disk.add({'new' : Item(contents=new_contents)})
  expected_disk.tweak('A/mu', contents=''.join(mu_contents))
  expected_disk.remove('A/B/E/beta')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/gamma', status='M ')
  expected_status.tweak('iota', status='M ')
  expected_status.add({'new' : Item(status='A ', wc_rev=0)})
  expected_status.tweak('A/mu', status='M ', wc_rev=2)
  expected_status.tweak('A/B/E/beta', status='D ')

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       [], True, True)

  # Retry
  expected_output.tweak(status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       [], True, True)

def patch_absolute_paths(sbox):
  "patch containing absolute paths"

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

  patch_file_path = os.path.abspath(sbox.get_tempname('my.patch'))

  os.chdir(wc_dir)

  # A patch with absolute paths.
  # The first diff points inside the working copy and should apply.
  # The second diff does not point inside the working copy so application
  # should fail.
  abs = os.path.abspath('.')
  if sys.platform == 'win32':
    abs = abs.replace("\\", "/")
  unidiff_patch = [
    "diff -ur A/B/E/alpha.orig A/B/E/alpha\n"
    "--- %s/A/B/E/alpha.orig\tThu Apr 16 19:49:53 2009\n" % abs,
    "+++ %s/A/B/E/alpha\tThu Apr 16 19:50:30 2009\n" % abs,
    "@@ -1 +1,2 @@\n",
    " This is the file 'alpha'.\n",
    "+Whoooo whooooo whoooooooo!\n",
    "diff -ur A/B/lambda.orig A/B/lambda\n"
    "--- /A/B/lambda.orig\tThu Apr 16 19:49:53 2009\n",
    "+++ /A/B/lambda\tThu Apr 16 19:51:25 2009\n",
    "@@ -1 +1 @@\n",
    "-This is the file 'lambda'.\n",
    "+It's the file 'lambda', who would have thought!\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  lambda_path = os.path.join(os.path.sep, 'A', 'B', 'lambda')
  expected_output = [
    'U         %s\n' % os.path.join('A', 'B', 'E', 'alpha'),
    'Skipped missing target: \'%s\'\n' % lambda_path,
  ] + svntest.main.summary_of_conflicts(skipped_paths=1)

  alpha_contents = "This is the file 'alpha'.\nWhoooo whooooo whoooooooo!\n"

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/B/E/alpha', contents=alpha_contents)

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

  expected_skip = wc.State('', {
    lambda_path:  Item(verb='Skipped missing target'),
  })

  svntest.actions.run_and_verify_patch('', patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_offset(sbox):
  "patch with offset searching"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = os.path.abspath(sbox.get_tempname('my.patch'))
  mu_path = sbox.ospath('A/mu')
  iota_path = sbox.ospath('iota')

  mu_contents = [
    "Dear internet user,\n",
    # The missing line here will cause the first hunk to match early
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "\n",
    "Again, we wish to congratulate you over your email success in our\n"
    "computer Balloting.\n",
  ]

  # iota's content will make both a late and early match possible.
  # The hunk to be applied is replicated here for reference:
  # @@ -5,6 +5,7 @@
  #  iota
  #  iota
  #  iota
  # +x
  #  iota
  #  iota
  #  iota
  #
  # This hunk wants to be applied at line 5, but that isn't
  # possible because line 8 ("zzz") does not match "iota".
  # The early match happens at line 2 (offset 3 = 5 - 2).
  # The late match happens at line 9 (offset 4 = 9 - 5).
  # Subversion will pick the early match in this case because it
  # is closer to line 5.
  iota_contents = [
    "iota\n",
    "iota\n",
    "iota\n",
    "iota\n",
    "iota\n",
    "iota\n",
    "iota\n",
    "zzz\n",
    "iota\n",
    "iota\n",
    "iota\n",
    "iota\n",
    "iota\n",
    "iota\n",
    "iota\n"
  ]

  # Set mu and iota contents
  svntest.main.file_write(mu_path, ''.join(mu_contents))
  svntest.main.file_write(iota_path, ''.join(iota_contents))
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu'       : Item(verb='Sending'),
    'iota'       : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  expected_status.tweak('iota', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)

  # Apply patch

  unidiff_patch = [
    "--- A/mu.orig	2009-06-24 15:23:55.000000000 +0100\n",
    "+++ A/mu	2009-06-24 15:21:23.000000000 +0100\n",
    "@@ -6,6 +6,9 @@\n",
    " through a computer ballot system drawn from over 100,000 company\n",
    " and 50,000,000 individual email addresses from all over the world.\n",
    " \n",
    "+It is a promotional program aimed at encouraging internet users;\n",
    "+therefore you do not need to buy ticket to enter for it.\n",
    "+\n",
    " Your email address drew and have won the sum of  750,000 Euros\n",
    " ( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    " file with\n",
    "@@ -14,11 +17,8 @@\n",
    "     BATCH NUMBERS :\n",
    "     EULO/1007/444/606/08;\n",
    "     SERIAL NUMBER: 45327\n",
    "-and PROMOTION DATE: 13th June. 2009\n",
    "+and PROMOTION DATE: 14th June. 2009\n",
    " \n",
    " To claim your winning prize, you are to contact the appointed\n",
    " agent below as soon as possible for the immediate release of your\n",
    " winnings with the below details.\n",
    "-\n",
    "-Again, we wish to congratulate you over your email success in our\n",
    "-computer Balloting.\n",
    "Index: iota\n",
    "===================================================================\n",
    "--- iota	(revision XYZ)\n",
    "+++ iota	(working copy)\n",
    "@@ -5,6 +5,7 @@\n",
    " iota\n",
    " iota\n",
    " iota\n",
    "+x\n",
    " iota\n",
    " iota\n",
    " iota\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  mu_contents = [
    "Dear internet user,\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "It is a promotional program aimed at encouraging internet users;\n",
    "therefore you do not need to buy ticket to enter for it.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 14th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
  ]

  iota_contents = [
    "iota\n",
    "iota\n",
    "iota\n",
    "iota\n",
    "x\n",
    "iota\n",
    "iota\n",
    "iota\n",
    "zzz\n",
    "iota\n",
    "iota\n",
    "iota\n",
    "iota\n",
    "iota\n",
    "iota\n",
    "iota\n",
  ]

  os.chdir(wc_dir)

  expected_output = [
    'U         %s\n' % os.path.join('A', 'mu'),
    '>         applied hunk @@ -6,6 +6,9 @@ with offset -1\n',
    '>         applied hunk @@ -14,11 +17,8 @@ with offset 4\n',
    'U         iota\n',
    '>         applied hunk @@ -5,6 +5,7 @@ with offset -3\n',
  ]

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/mu', contents=''.join(mu_contents))
  expected_disk.tweak('iota', contents=''.join(iota_contents))

  expected_status = svntest.actions.get_virginal_state('', 1)
  expected_status.tweak('A/mu', status='M ', wc_rev=2)
  expected_status.tweak('iota', status='M ', wc_rev=2)

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch('', patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_chopped_leading_spaces(sbox):
  "patch with chopped leading spaces"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  mu_path = sbox.ospath('A/mu')

  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "\n",
    "Again, we wish to congratulate you over your email success in our\n"
    "computer Balloting.\n"
  ]

  # Set mu contents
  svntest.main.file_write(mu_path, ''.join(mu_contents))
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu'       : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)

  # Apply patch

  unidiff_patch = [
    "Index: A/D/gamma\n",
    "===================================================================\n",
    "--- A/D/gamma\t(revision 1)\n",
    "+++ A/D/gamma\t(working copy)\n",
    "@@ -1 +1 @@\n",
    "-This is the file 'gamma'.\n",
    "+It is the file 'gamma'.\n",
    "Index: iota\n",
    "===================================================================\n",
    "--- iota\t(revision 1)\n",
    "+++ iota\t(working copy)\n",
    "@@ -1 +1,2 @@\n",
    " This is the file 'iota'.\n",
    "+Some more bytes\n",
    "\n",
    "Index: new\n",
    "===================================================================\n",
    "--- new	(revision 0)\n",
    "+++ new	(revision 0)\n",
    "@@ -0,0 +1 @@\n",
    "+new\n",
    "\n",
    "--- A/mu.orig	2009-06-24 15:23:55.000000000 +0100\n",
    "+++ A/mu	2009-06-24 15:21:23.000000000 +0100\n",
    "@@ -6,6 +6,9 @@\n",
    " through a computer ballot system drawn from over 100,000 company\n",
    " and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "+It is a promotional program aimed at encouraging internet users;\n",
    "+therefore you do not need to buy ticket to enter for it.\n",
    "+\n",
    " Your email address drew and have won the sum of  750,000 Euros\n",
    " ( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    " file with\n",
    "@@ -14,11 +17,8 @@\n",
    "     BATCH NUMBERS :\n",
    "     EULO/1007/444/606/08;\n",
    "     SERIAL NUMBER: 45327\n",
    "-and PROMOTION DATE: 13th June. 2009\n",
    "+and PROMOTION DATE: 14th June. 2009\n",
    "\n",
    " To claim your winning prize, you are to contact the appointed\n",
    " agent below as soon as possible for the immediate release of your\n",
    " winnings with the below details.\n",
    "-\n",
    "-Again, we wish to congratulate you over your email success in our\n",
    "-computer Balloting.\n",
    "Index: A/B/E/beta\n",
    "===================================================================\n",
    "--- A/B/E/beta	(revision 1)\n",
    "+++ A/B/E/beta	(working copy)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'beta'.\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  gamma_contents = "It is the file 'gamma'.\n"
  iota_contents = "This is the file 'iota'.\nSome more bytes\n"
  new_contents = "new\n"
  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "It is a promotional program aimed at encouraging internet users;\n",
    "therefore you do not need to buy ticket to enter for it.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 14th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
  ]

  expected_output = [
    'U         %s\n' % sbox.ospath('A/D/gamma'),
    'U         %s\n' % sbox.ospath('iota'),
    'A         %s\n' % sbox.ospath('new'),
    'U         %s\n' % sbox.ospath('A/mu'),
    'D         %s\n' % sbox.ospath('A/B/E/beta'),
  ]

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/D/gamma', contents=gamma_contents)
  expected_disk.tweak('iota', contents=iota_contents)
  expected_disk.add({'new' : Item(contents=new_contents)})
  expected_disk.tweak('A/mu', contents=''.join(mu_contents))
  expected_disk.remove('A/B/E/beta')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/gamma', status='M ')
  expected_status.tweak('iota', status='M ')
  expected_status.add({'new' : Item(status='A ', wc_rev=0)})
  expected_status.tweak('A/mu', status='M ', wc_rev=2)
  expected_status.tweak('A/B/E/beta', status='D ')

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run


def patch_strip1(sbox):
  "patch with --strip 1"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  mu_path = sbox.ospath('A/mu')

  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "\n",
    "Again, we wish to congratulate you over your email success in our\n"
    "computer Balloting.\n"
  ]

  # Set mu contents
  svntest.main.file_write(mu_path, ''.join(mu_contents))
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu'       : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)

  # Apply patch

  unidiff_patch = [
    "Index: b/A/D/gamma\n",
    "===================================================================\n",
    "--- a/A/D/gamma\t(revision 1)\n",
    "+++ b/A/D/gamma\t(working copy)\n",
    "@@ -1 +1 @@\n",
    "-This is the file 'gamma'.\n",
    "+It is the file 'gamma'.\n",
    "Index: x/iota\n",
    "===================================================================\n",
    "--- x/iota\t(revision 1)\n",
    "+++ x/iota\t(working copy)\n",
    "@@ -1 +1,2 @@\n",
    " This is the file 'iota'.\n",
    "+Some more bytes\n",
    "\n",
    "Index: /new\n",
    "===================================================================\n",
    "--- /new	(revision 0)\n",
    "+++ /new	(revision 0)\n",
    "@@ -0,0 +1 @@\n",
    "+new\n",
    "\n",
    "--- x/A/mu.orig	2009-06-24 15:23:55.000000000 +0100\n",
    "+++ x/A/mu	2009-06-24 15:21:23.000000000 +0100\n",
    "@@ -6,6 +6,9 @@\n",
    " through a computer ballot system drawn from over 100,000 company\n",
    " and 50,000,000 individual email addresses from all over the world.\n",
    " \n",
    "+It is a promotional program aimed at encouraging internet users;\n",
    "+therefore you do not need to buy ticket to enter for it.\n",
    "+\n",
    " Your email address drew and have won the sum of  750,000 Euros\n",
    " ( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    " file with\n",
    "@@ -14,11 +17,8 @@\n",
    "     BATCH NUMBERS :\n",
    "     EULO/1007/444/606/08;\n",
    "     SERIAL NUMBER: 45327\n",
    "-and PROMOTION DATE: 13th June. 2009\n",
    "+and PROMOTION DATE: 14th June. 2009\n",
    " \n",
    " To claim your winning prize, you are to contact the appointed\n",
    " agent below as soon as possible for the immediate release of your\n",
    " winnings with the below details.\n",
    "-\n",
    "-Again, we wish to congratulate you over your email success in our\n",
    "-computer Balloting.\n",
    "Index: A/B/E/beta\n",
    "===================================================================\n",
    "--- /A/B/E/beta	(revision 1)\n",
    "+++ /A/B/E/beta	(working copy)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'beta'.\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  gamma_contents = "It is the file 'gamma'.\n"
  iota_contents = "This is the file 'iota'.\nSome more bytes\n"
  new_contents = "new\n"
  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "It is a promotional program aimed at encouraging internet users;\n",
    "therefore you do not need to buy ticket to enter for it.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 14th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
  ]

  expected_output = [
    'U         %s\n' % sbox.ospath('A/D/gamma'),
    'U         %s\n' % sbox.ospath('iota'),
    'A         %s\n' % sbox.ospath('new'),
    'U         %s\n' % sbox.ospath('A/mu'),
    'D         %s\n' % sbox.ospath('A/B/E/beta'),
  ]

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/D/gamma', contents=gamma_contents)
  expected_disk.tweak('iota', contents=iota_contents)
  expected_disk.add({'new' : Item(contents=new_contents)})
  expected_disk.tweak('A/mu', contents=''.join(mu_contents))
  expected_disk.remove('A/B/E/beta')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/gamma', status='M ')
  expected_status.tweak('iota', status='M ')
  expected_status.add({'new' : Item(status='A ', wc_rev=0)})
  expected_status.tweak('A/mu', status='M ', wc_rev=2)
  expected_status.tweak('A/B/E/beta', status='D ')

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1, # dry-run
                                       '--strip', '1')

def patch_no_index_line(sbox):
  "patch with no index lines"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  gamma_path = sbox.ospath('A/D/gamma')
  iota_path = sbox.ospath('iota')

  gamma_contents = [
    "\n",
    "Another line before\n",
    "A third line before\n",
    "This is the file 'gamma'.\n",
    "A line after\n",
    "Another line after\n",
    "A third line after\n",
  ]

  svntest.main.file_write(gamma_path, ''.join(gamma_contents))
  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)
  unidiff_patch = [
    "--- A/D/gamma\t(revision 1)\n",
    "+++ A/D/gamma\t(working copy)\n",
    "@@ -1,7 +1,7 @@\n",
    " \n",
    " Another line before\n",
    " A third line before\n",
    "-This is the file 'gamma'.\n",
    "+It is the file 'gamma'.\n",
    " A line after\n",
    " Another line after\n",
    " A third line after\n",
    "--- iota\t(revision 1)\n",
    "+++ iota\t(working copy)\n",
    "@@ -1 +1,2 @@\n",
    " This is the file 'iota'.\n",
    "+Some more bytes\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  gamma_contents = [
    "\n",
    "Another line before\n",
    "A third line before\n",
    "It is the file 'gamma'.\n",
    "A line after\n",
    "Another line after\n",
    "A third line after\n",
  ]
  iota_contents = [
    "This is the file 'iota'.\n",
    "Some more bytes\n",
  ]
  expected_output = [
    'U         %s\n' % sbox.ospath('A/D/gamma'),
    'U         %s\n' % sbox.ospath('iota'),
  ]

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/D/gamma', contents=''.join(gamma_contents))
  expected_disk.tweak('iota', contents=''.join(iota_contents))

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/gamma', status='M ', wc_rev=2)
  expected_status.tweak('iota', status='M ', wc_rev=1)

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_add_new_dir(sbox):
  "patch with missing dirs"

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

  patch_file_path = sbox.get_tempname('my.patch')

  # The first diff is adding 'new' with two missing dirs. The second is
  # adding 'new' with one missing dir to a 'A/B/E' that is locally deleted
  # (should be skipped). The third is adding 'new' to 'A/C' that is locally
  # deleted (should be skipped too). The fourth is adding 'new' with a
  # directory that is unversioned (should be skipped as well).
  unidiff_patch = [
    "Index: new\n",
    "===================================================================\n",
    "--- X/Y/new\t(revision 0)\n",
    "+++ X/Y/new\t(revision 0)\n",
    "@@ -0,0 +1 @@\n",
    "+new\n",
    "Index: new\n",
    "===================================================================\n",
    "--- A/B/E/Y/new\t(revision 0)\n",
    "+++ A/B/E/Y/new\t(revision 0)\n",
    "@@ -0,0 +1 @@\n",
    "+new\n",
    "Index: new\n",
    "===================================================================\n",
    "--- A/C/new\t(revision 0)\n",
    "+++ A/C/new\t(revision 0)\n",
    "@@ -0,0 +1 @@\n",
    "+new\n",
    "Index: new\n",
    "===================================================================\n",
    "--- A/Z/new\t(revision 0)\n",
    "+++ A/Z/new\t(revision 0)\n",
    "@@ -0,0 +1 @@\n",
    "+new\n",
  ]

  C_path = sbox.ospath('A/C')
  E_path = sbox.ospath('A/B/E')

  svntest.main.safe_rmtree(C_path)
  svntest.main.safe_rmtree(E_path)
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  A_B_E_Y_new_path = sbox.ospath('A/B/E/Y/new')
  A_C_new_path = sbox.ospath('A/C/new')
  A_Z_new_path = sbox.ospath('A/Z/new')
  expected_output = [
    'A         %s\n' % sbox.ospath('X'),
    'A         %s\n' % sbox.ospath('X/Y'),
    'A         %s\n' % sbox.ospath('X/Y/new'),
    'Skipped missing target: \'%s\'\n' % A_B_E_Y_new_path,
    'Skipped missing target: \'%s\'\n' % A_C_new_path,
    'Skipped missing target: \'%s\'\n' % A_Z_new_path,
  ] + svntest.main.summary_of_conflicts(skipped_paths=3)

  # Create the unversioned obstructing directory
  os.mkdir(os.path.dirname(A_Z_new_path))

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
           'X/Y/new'   : Item(contents='new\n'),
           'A/Z'       : Item()
  })
  expected_disk.remove('A/B/E', 'A/B/E/alpha', 'A/B/E/beta', 'A/C')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
           'X'          : Item(status='A ', wc_rev=0),
           'X/Y'        : Item(status='A ', wc_rev=0),
           'X/Y/new'    : Item(status='A ', wc_rev=0),
           'A/B/E'      : Item(status='! ', wc_rev=1),
           'A/B/E/alpha': Item(status='! ', wc_rev=1),
           'A/B/E/beta' : Item(status='! ', wc_rev=1),
           'A/C'        : Item(status='! ', wc_rev=1),
  })

  expected_skip = wc.State(
    '',
    {A_Z_new_path     : Item(verb='Skipped missing target'),
     A_B_E_Y_new_path : Item(verb='Skipped missing target'),
     A_C_new_path     : Item(verb='Skipped missing target')})

  svntest.actions.run_and_verify_patch(wc_dir,
                                       patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run
def patch_remove_empty_dirs(sbox):
  "patch deleting all children of a directory"

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

  patch_file_path = sbox.get_tempname('my.patch')

  # Contents of B:
  # A/B/lamba
  # A/B/F
  # A/B/E/{alpha,beta}
  # Before patching we've deleted F, which means that B is empty after patching and
  # should be removed.
  #
  # Contents of H:
  # A/D/H/{chi,psi,omega}
  # Before patching, chi has been removed by a non-svn operation which means it has
  # status missing. The patch deletes the other two files but should not delete H.

  unidiff_patch = [
    "Index: psi\n",
    "===================================================================\n",
    "--- A/D/H/psi\t(revision 0)\n",
    "+++ A/D/H/psi\t(revision 0)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'psi'.\n",
    "Index: omega\n",
    "===================================================================\n",
    "--- A/D/H/omega\t(revision 0)\n",
    "+++ A/D/H/omega\t(revision 0)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'omega'.\n",
    "Index: lambda\n",
    "===================================================================\n",
    "--- A/B/lambda\t(revision 0)\n",
    "+++ A/B/lambda\t(revision 0)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'lambda'.\n",
    "Index: alpha\n",
    "===================================================================\n",
    "--- A/B/E/alpha\t(revision 0)\n",
    "+++ A/B/E/alpha\t(revision 0)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'alpha'.\n",
    "Index: beta\n",
    "===================================================================\n",
    "--- A/B/E/beta\t(revision 0)\n",
    "+++ A/B/E/beta\t(revision 0)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'beta'.\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  F_path = sbox.ospath('A/B/F')
  svntest.actions.run_and_verify_svn(None, [],
                                     'rm', F_path)
  svntest.actions.run_and_verify_svn(None, [],
                                     'up', wc_dir)

  # We should be able to handle one path beeing missing.
  os.remove(sbox.ospath('A/D/H/chi'))

  expected_output = [
    'D         %s\n' % sbox.ospath('A/D/H/psi'),
    'D         %s\n' % sbox.ospath('A/D/H/omega'),
    'D         %s\n' % sbox.ospath('A/B/lambda'),
    'D         %s\n' % sbox.ospath('A/B/E/alpha'),
    'D         %s\n' % sbox.ospath('A/B/E/beta'),
    'D         %s\n' % sbox.ospath('A/B/E'),
    'D         %s\n' % sbox.ospath('A/B'),
  ]

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/D/H/chi',
                       'A/D/H/psi',
                       'A/D/H/omega',
                       'A/B/lambda',
                       'A/B',
                       'A/B/E',
                       'A/B/E/alpha',
                       'A/B/E/beta',
                       'A/B/F')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/H/chi', status='! ')
  expected_status.tweak('A/D/H/omega', 'A/D/H/psi', 'A/B', 'A/B/E',
                        'A/B/E/beta', 'A/B/E/alpha', 'A/B/lambda',
                        'A/B/F', status='D ')

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir,
                                       patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run


def patch_reject(sbox):
  "patch which is rejected"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Set gamma contents
  gamma_contents = "Hello there! I'm the file 'gamma'.\n"
  gamma_path = sbox.ospath('A/D/gamma')
  svntest.main.file_write(gamma_path, gamma_contents)
  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)

  patch_file_path = sbox.get_tempname('my.patch')

  # Apply patch

  unidiff_patch = [
    "Index: A/D/gamma\n",
    "===================================================================\n",
    "--- A/D/gamma\t(revision 1)\n",
    "+++ A/D/gamma\t(working copy)\n",
    "@@ -1 +1 @@\n",
    "-This is really the file 'gamma'.\n",
    "+It is really the file 'gamma'.\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  expected_output = [
    'C         %s\n' % sbox.ospath('A/D/gamma'),
    '>         rejected hunk @@ -1,1 +1,1 @@\n',
  ] + svntest.main.summary_of_conflicts(text_conflicts=1)

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/D/gamma', contents=gamma_contents)

  reject_file_contents = [
    "--- A/D/gamma\n",
    "+++ A/D/gamma\n",
    "@@ -1,1 +1,1 @@\n",
    "-This is really the file 'gamma'.\n",
    "+It is really the file 'gamma'.\n",
  ]
  expected_disk.add({'A/D/gamma.svnpatch.rej' :
                     Item(contents=''.join(reject_file_contents))})

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/gamma', wc_rev=2)
  # ### not yet
  #expected_status.tweak('A/D/gamma', status='C ')

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_keywords(sbox):
  "patch containing keywords"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Set gamma contents
  gamma_contents = "$Rev$\nHello there! I'm the file 'gamma'.\n"
  gamma_path = sbox.ospath('A/D/gamma')
  svntest.main.file_write(gamma_path, gamma_contents)
  # Expand the keyword
  svntest.main.run_svn(None, 'propset', 'svn:keywords', 'Rev',
                       sbox.ospath('A/D/gamma'))
  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)

  patch_file_path = sbox.get_tempname('my.patch')

  # Apply patch

  unidiff_patch = [
   "Index: gamma\n",
   "===================================================================\n",
   "--- A/D/gamma	(revision 3)\n",
   "+++ A/D/gamma	(working copy)\n",
   "@@ -1,2 +1,3 @@\n",
   " $Rev$\n",
   " Hello there! I'm the file 'gamma'.\n",
   "+booo\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  expected_output = [
    'U         %s\n' % sbox.ospath('A/D/gamma'),
  ]

  expected_disk = svntest.main.greek_state.copy()
  gamma_contents = "$Rev: 2 $\nHello there! I'm the file 'gamma'.\nbooo\n"
  expected_disk.tweak('A/D/gamma', contents=gamma_contents,
                      props={'svn:keywords' : 'Rev'})

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/gamma', status='M ', wc_rev=2)

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_with_fuzz(sbox):
  "patch with fuzz"

  sbox.build()
  wc_dir = sbox.wc_dir
  patch_file_path = sbox.get_tempname('my.patch')

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

  # We have replaced a couple of lines to cause fuzz. Those lines contains
  # the word fuzz
  mu_contents = [
    "Line replaced for fuzz = 1\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "Line replaced for fuzz = 2 with only the second context line changed\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "This line is inserted to cause an offset of +1\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "\n",
    "Line replaced for fuzz = 2\n",
    "Line replaced for fuzz = 2\n",
  ]

  # Set mu contents
  svntest.main.file_write(mu_path, ''.join(mu_contents))
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu'       : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                      expected_status)

  unidiff_patch = [
    "Index: mu\n",
    "===================================================================\n",
    "--- A/mu\t(revision 0)\n",
    "+++ A/mu\t(revision 0)\n",
    "@@ -1,6 +1,7 @@\n",
    " Dear internet user,\n",
    " \n",
    " We wish to congratulate you over your email success in our computer\n",
    "+A new line here\n",
    " Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    " in which email addresses were used. All participants were selected\n",
    " through a computer ballot system drawn from over 100,000 company\n",
    "@@ -7,7 +8,9 @@\n",
    " and 50,000,000 individual email addresses from all over the world.\n",
    " \n",
    " Your email address drew and have won the sum of  750,000 Euros\n",
    "+Another new line\n",
    " ( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "+A third new line\n",
    " file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "@@ -19,6 +20,7 @@\n",
    " To claim your winning prize, you are to contact the appointed\n",
    " agent below as soon as possible for the immediate release of your\n",
    " winnings with the below details.\n",
    "+A fourth new line\n",
    " \n",
    " Again, we wish to congratulate you over your email success in our\n"
    " computer Balloting. [No trailing newline here]"
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  mu_contents = [
    "Line replaced for fuzz = 1\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "A new line here\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "Line replaced for fuzz = 2 with only the second context line changed\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "Another new line\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "A third new line\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "This line is inserted to cause an offset of +1\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "A fourth new line\n",
    "\n",
    "Line replaced for fuzz = 2\n",
    "Line replaced for fuzz = 2\n",
  ]

  expected_output = [
    'U         %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk @@ -1,6 +1,7 @@ with fuzz 1\n',
    '>         applied hunk @@ -7,7 +8,9 @@ with fuzz 2\n',
    '>         applied hunk @@ -19,6 +20,7 @@ with offset 1 and fuzz 2\n',
  ]
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/mu', contents=''.join(mu_contents))

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='M ', wc_rev=2)

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_reverse(sbox):
  "patch in reverse"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  mu_path = sbox.ospath('A/mu')

  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "\n",
    "Again, we wish to congratulate you over your email success in our\n"
    "computer Balloting.\n"
  ]

  # Set mu contents
  svntest.main.file_write(mu_path, ''.join(mu_contents))
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu'       : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)

  # Apply patch

  unidiff_patch = [
    "Index: A/D/gamma\n",
    "===================================================================\n",
    "--- A/D/gamma\t(revision 1)\n",
    "+++ A/D/gamma\t(working copy)\n",
    "@@ -1 +1 @@\n",
    "+This is the file 'gamma'.\n",
    "-It is the file 'gamma'.\n",
    "Index: iota\n",
    "===================================================================\n",
    "--- iota\t(revision 1)\n",
    "+++ iota\t(working copy)\n",
    "@@ -1,2 +1 @@\n",
    " This is the file 'iota'.\n",
    "-Some more bytes\n",
    "\n",
    "Index: new\n",
    "===================================================================\n",
    "--- new	(revision 0)\n",
    "+++ new	(revision 0)\n",
    "@@ -1 +0,0 @@\n",
    "-new\n",
    "\n",
    "--- A/mu	2009-06-24 15:23:55.000000000 +0100\n",
    "+++ A/mu.orig	2009-06-24 15:21:23.000000000 +0100\n",
    "@@ -6,9 +6,6 @@\n",
    " through a computer ballot system drawn from over 100,000 company\n",
    " and 50,000,000 individual email addresses from all over the world.\n",
    " \n",
    "-It is a promotional program aimed at encouraging internet users;\n",
    "-therefore you do not need to buy ticket to enter for it.\n",
    "-\n",
    " Your email address drew and have won the sum of  750,000 Euros\n",
    " ( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    " file with\n",
    "@@ -17,8 +14,11 @@\n",
    "     BATCH NUMBERS :\n",
    "     EULO/1007/444/606/08;\n",
    "     SERIAL NUMBER: 45327\n",
    "+and PROMOTION DATE: 13th June. 2009\n",
    "-and PROMOTION DATE: 14th June. 2009\n",
    " \n",
    " To claim your winning prize, you are to contact the appointed\n",
    " agent below as soon as possible for the immediate release of your\n",
    " winnings with the below details.\n",
    "+\n",
    "+Again, we wish to congratulate you over your email success in our\n",
    "+computer Balloting.\n",
    "Index: A/B/E/beta\n",
    "===================================================================\n",
    "--- A/B/E/beta	(working copy)\n",
    "+++ A/B/E/beta	(revision 1)\n",
    "@@ -0,0 +1 @@\n",
    "+This is the file 'beta'.\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  gamma_contents = "It is the file 'gamma'.\n"
  iota_contents = "This is the file 'iota'.\nSome more bytes\n"
  new_contents = "new\n"
  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "It is a promotional program aimed at encouraging internet users;\n",
    "therefore you do not need to buy ticket to enter for it.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 14th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
  ]

  expected_output = [
    'U         %s\n' % sbox.ospath('A/D/gamma'),
    'U         %s\n' % sbox.ospath('iota'),
    'A         %s\n' % sbox.ospath('new'),
    'U         %s\n' % sbox.ospath('A/mu'),
    'D         %s\n' % sbox.ospath('A/B/E/beta'),
  ]

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/D/gamma', contents=gamma_contents)
  expected_disk.tweak('iota', contents=iota_contents)
  expected_disk.add({'new' : Item(contents=new_contents)})
  expected_disk.tweak('A/mu', contents=''.join(mu_contents))
  expected_disk.remove('A/B/E/beta')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/gamma', status='M ')
  expected_status.tweak('iota', status='M ')
  expected_status.add({'new' : Item(status='A ', wc_rev=0)})
  expected_status.tweak('A/mu', status='M ', wc_rev=2)
  expected_status.tweak('A/B/E/beta', status='D ')

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1, # dry-run
                                       '--reverse-diff')

def patch_no_svn_eol_style(sbox):
  "patch target with no svn:eol-style"

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

  patch_file_path = sbox.get_tempname('my.patch')
  mu_path = sbox.ospath('A/mu')

  crlf = '\r\n'

  eols = [crlf, '\015', '\n', '\012']
  for target_eol in eols:
    for patch_eol in eols:
      mu_contents = [
        "We wish to congratulate you over your email success in our computer",
        target_eol,
        "Balloting. This is a Millennium Scientific Electronic Computer Draw",
        target_eol,
        "in which email addresses were used. All participants were selected",
        target_eol,
        "through a computer ballot system drawn from over 100,000 company",
        target_eol,
        "and 50,000,000 individual email addresses from all over the world.",
        target_eol,
        "It is a promotional program aimed at encouraging internet users;",
        target_eol,
      ]

      # Set mu contents
      svntest.main.file_write(mu_path, ''.join(mu_contents), mode='wb')

      unidiff_patch = [
        "Index: mu",
        patch_eol,
        "===================================================================",
        patch_eol,
        "--- A/mu\t(revision 0)",
        patch_eol,
        "+++ A/mu\t(revision 0)",
        patch_eol,
        "@@ -1,5 +1,6 @@",
        patch_eol,
        " We wish to congratulate you over your email success in our computer",
        patch_eol,
        " Balloting. This is a Millennium Scientific Electronic Computer Draw",
        patch_eol,
        "+A new line here",
        patch_eol,
        " in which email addresses were used. All participants were selected",
        patch_eol,
        " through a computer ballot system drawn from over 100,000 company",
        patch_eol,
        " and 50,000,000 individual email addresses from all over the world.",
        patch_eol,
      ]

      mu_contents = [
        "We wish to congratulate you over your email success in our computer",
        patch_eol,
        "Balloting. This is a Millennium Scientific Electronic Computer Draw",
        patch_eol,
        "A new line here",
        patch_eol,
        "in which email addresses were used. All participants were selected",
        patch_eol,
        "through a computer ballot system drawn from over 100,000 company",
        patch_eol,
        "and 50,000,000 individual email addresses from all over the world.",
        patch_eol,
        "It is a promotional program aimed at encouraging internet users;",
        target_eol,
      ]

      svntest.main.file_write(patch_file_path, ''.join(unidiff_patch),
                              mode='wb')

      expected_output = wc.State(wc_dir, {
        'A/mu' : Item(status='U '),
      })
      expected_disk = svntest.main.greek_state.copy()
      expected_disk.tweak('A/mu', contents=''.join(mu_contents))

      expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
      expected_status.tweak('A/mu', status='M ', wc_rev=1)

      expected_skip = wc.State('', { })

      svntest.actions.run_and_verify_patch2(wc_dir,
                                            patch_file_path,
                                            expected_output,
                                            expected_disk,
                                            expected_status,
                                            expected_skip,
                                            [], True, True,
                                            keep_eol_style=True)

      expected_output = ["Reverted '" + mu_path + "'\n"]
      svntest.actions.run_and_verify_svn(expected_output, [],
                                         'revert', '-R', wc_dir)

def patch_with_svn_eol_style(sbox):
  "patch target with svn:eol-style"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  mu_path = sbox.ospath('A/mu')

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

  eols = [crlf, '\015', native_nl, '\012']
  eol_styles = ['CRLF', 'CR', 'native', 'LF']
  rev = 1
  for target_eol, target_eol_style in zip(eols, eol_styles):
    for patch_eol in eols:
      mu_contents = [
        "We wish to congratulate you over your email success in our computer",
        target_eol,
        "Balloting. This is a Millennium Scientific Electronic Computer Draw",
        target_eol,
        "in which email addresses were used. All participants were selected",
        target_eol,
        "through a computer ballot system drawn from over 100,000 company",
        target_eol,
        "and 50,000,000 individual email addresses from all over the world.",
        target_eol,
        "It is a promotional program aimed at encouraging internet users;",
        target_eol,
      ]

      # Set mu contents
      svntest.main.run_svn(None, 'rm', mu_path)
      svntest.main.run_svn(None, 'commit', '-m', 'delete mu', mu_path)
      svntest.main.file_write(mu_path, ''.join(mu_contents), mode='wb')
      svntest.main.run_svn(None, 'add', mu_path)
      svntest.main.run_svn(None, 'propset', 'svn:eol-style', target_eol_style,
                           mu_path)
      svntest.main.run_svn(None, 'commit', '-m', 'set eol-style', mu_path)

      unidiff_patch = [
        "Index: mu",
        patch_eol,
        "===================================================================",
        patch_eol,
        "--- A/mu\t(revision 0)",
        patch_eol,
        "+++ A/mu\t(revision 0)",
        patch_eol,
        "@@ -1,5 +1,6 @@",
        patch_eol,
        " We wish to congratulate you over your email success in our computer",
        patch_eol,
        " Balloting. This is a Millennium Scientific Electronic Computer Draw",
        patch_eol,
        "+A new line here",
        patch_eol,
        " in which email addresses were used. All participants were selected",
        patch_eol,
        " through a computer ballot system drawn from over 100,000 company",
        patch_eol,
        " and 50,000,000 individual email addresses from all over the world.",
        patch_eol,
      ]

      mu_contents = [
        "We wish to congratulate you over your email success in our computer",
        target_eol,
        "Balloting. This is a Millennium Scientific Electronic Computer Draw",
        target_eol,
        "A new line here",
        target_eol,
        "in which email addresses were used. All participants were selected",
        target_eol,
        "through a computer ballot system drawn from over 100,000 company",
        target_eol,
        "and 50,000,000 individual email addresses from all over the world.",
        target_eol,
        "It is a promotional program aimed at encouraging internet users;",
        target_eol,
      ]

      svntest.main.file_write(patch_file_path, ''.join(unidiff_patch),
                              mode='wb')

      expected_output = [
        'U         %s\n' % sbox.ospath('A/mu'),
      ]
      expected_disk = svntest.main.greek_state.copy()
      expected_disk.tweak('A/mu', contents=''.join(mu_contents),
                          props={'svn:eol-style' : target_eol_style})

      expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
      rev += 2
      expected_status.tweak('A/mu', status='M ', wc_rev=rev)

      expected_skip = wc.State('', { })

      svntest.actions.run_and_verify_patch2(wc_dir,
                                            patch_file_path,
                                            expected_output,
                                            expected_disk,
                                            expected_status,
                                            expected_skip,
                                            None, # expected err
                                            1, # check-props
                                            1, # dry-run
                                            keep_eol_style=True)

      expected_output = ["Reverted '" + mu_path + "'\n"]
      svntest.actions.run_and_verify_svn(expected_output, [], 'revert', '-R', wc_dir)

def patch_with_svn_eol_style_uncommitted(sbox):
  "patch target with uncommitted svn:eol-style"

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

  patch_file_path = sbox.get_tempname('my.patch')
  mu_path = sbox.ospath('A/mu')

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

  eols = [crlf, '\015', native_nl, '\012']
  eol_styles = ['CRLF', 'CR', 'native', 'LF']
  for target_eol, target_eol_style in zip(eols, eol_styles):
    for patch_eol in eols:
      mu_contents = [
        "We wish to congratulate you over your email success in our computer",
        '\n',
        "Balloting. This is a Millennium Scientific Electronic Computer Draw",
        '\n',
        "in which email addresses were used. All participants were selected",
        '\n',
        "through a computer ballot system drawn from over 100,000 company",
        '\n',
        "and 50,000,000 individual email addresses from all over the world.",
        '\n',
        "It is a promotional program aimed at encouraging internet users;",
        '\n',
      ]

      # Set mu contents
      svntest.main.file_write(mu_path, ''.join(mu_contents), mode='wb')
      svntest.main.run_svn(None, 'propset', 'svn:eol-style', target_eol_style,
                           mu_path)

      unidiff_patch = [
        "Index: mu",
        patch_eol,
        "===================================================================",
        patch_eol,
        "--- A/mu\t(revision 0)",
        patch_eol,
        "+++ A/mu\t(revision 0)",
        patch_eol,
        "@@ -1,5 +1,6 @@",
        patch_eol,
        " We wish to congratulate you over your email success in our computer",
        patch_eol,
        " Balloting. This is a Millennium Scientific Electronic Computer Draw",
        patch_eol,
        "+A new line here",
        patch_eol,
        " in which email addresses were used. All participants were selected",
        patch_eol,
        " through a computer ballot system drawn from over 100,000 company",
        patch_eol,
        " and 50,000,000 individual email addresses from all over the world.",
        patch_eol,
      ]

      mu_contents = [
        "We wish to congratulate you over your email success in our computer",
        target_eol,
        "Balloting. This is a Millennium Scientific Electronic Computer Draw",
        target_eol,
        "A new line here",
        target_eol,
        "in which email addresses were used. All participants were selected",
        target_eol,
        "through a computer ballot system drawn from over 100,000 company",
        target_eol,
        "and 50,000,000 individual email addresses from all over the world.",
        target_eol,
        "It is a promotional program aimed at encouraging internet users;",
        target_eol,
      ]

      svntest.main.file_write(patch_file_path, ''.join(unidiff_patch),
                              mode='wb')

      expected_output = wc.State(wc_dir, {
        'A/mu' : Item(status='U '),
      })
      expected_disk = svntest.main.greek_state.copy()
      expected_disk.tweak('A/mu', contents=''.join(mu_contents),
                          props={'svn:eol-style' : target_eol_style})

      expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
      expected_status.tweak('A/mu', status='MM', wc_rev=1)

      expected_skip = wc.State('', { })

      svntest.actions.run_and_verify_patch2(wc_dir,
                                            patch_file_path,
                                            expected_output,
                                            expected_disk,
                                            expected_status,
                                            expected_skip,
                                            None, # expected err
                                            1, # check-props
                                            1, # dry-run
                                            keep_eol_style=True)

      expected_output = ["Reverted '" + mu_path + "'\n"]
      svntest.actions.run_and_verify_svn(expected_output, [], 'revert', '-R', wc_dir)

def patch_with_ignore_whitespace(sbox):
  "ignore whitespace when patching"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  mu_path = sbox.ospath('A/mu')

  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company \n",
    "and 50,000,000\t\tindividual email addresses from all over the world. \n",
    " \n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "\n",
    "Again, we wish to congratulate you over your email success in our\n"
    "computer Balloting.\n"
  ]

  # Set mu contents
  svntest.main.file_write(mu_path, ''.join(mu_contents))
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu'       : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)

  # Apply patch with leading and trailing spaces removed and tabs transformed
  # to spaces. The patch should match and the hunks should be written to the
  # target as-is.

  unidiff_patch = [
    "Index: A/mu\n",
    "===================================================================\n",
    "--- A/mu.orig	2009-06-24 15:23:55.000000000 +0100\n",
    "+++ A/mu	2009-06-24 15:21:23.000000000 +0100\n",
    "@@ -6,6 +6,9 @@\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "+It is a promotional program aimed at encouraging internet users;\n",
    "+therefore you do not need to buy ticket to enter for it.\n",
    "+\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "@@ -14,11 +17,8 @@\n",
    "BATCH NUMBERS :\n",
    "EULO/1007/444/606/08;\n",
    "SERIAL NUMBER: 45327\n",
    "-and PROMOTION DATE: 13th June. 2009\n",
    "+and PROMOTION DATE: 14th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "-\n",
    "-Again, we wish to congratulate you over your email success in our\n",
    "-computer Balloting.\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  gamma_contents = "It is the file 'gamma'.\n"
  iota_contents = "This is the file 'iota'.\nSome more bytes\n"
  new_contents = "new\n"
  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "It is a promotional program aimed at encouraging internet users;\n",
    "therefore you do not need to buy ticket to enter for it.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "BATCH NUMBERS :\n",
    "EULO/1007/444/606/08;\n",
    "SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 14th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
  ]

  expected_output = [
    'U         %s\n' % sbox.ospath('A/mu'),
  ]

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

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='M ', wc_rev=2)

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1, # dry-run
                                       "--ignore-whitespace",)

def patch_replace_locally_deleted_file(sbox):
  "patch that replaces a locally deleted file"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  mu_path = sbox.ospath('A/mu')

  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "\n",
    "Again, we wish to congratulate you over your email success in our\n"
    "computer Balloting.\n"
  ]

  # Set mu contents
  svntest.main.file_write(mu_path, ''.join(mu_contents))
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu'       : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)

  # Locally delete mu
  svntest.main.run_svn(None, 'rm', mu_path)

  # Apply patch that re-creates mu

  unidiff_patch = [
    "===================================================================\n",
    "--- A/mu.orig	2009-06-24 15:23:55.000000000 +0100\n",
    "+++ A/mu	2009-06-24 15:21:23.000000000 +0100\n",
    "@@ -0,0 +1 @@\n",
    "+new\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  mu_contents = "new\n"

  expected_output = [
    'A         %s\n' % mu_path,
  ]

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

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='R ', wc_rev=2)

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run
# Regression test for #3643
def patch_no_eol_at_eof(sbox):
  "patch with no eol at eof"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  iota_path = sbox.ospath('iota')

  iota_contents = [
    "One line\n",
    "Another line\n",
    "A third line \n",
    "This is the file 'iota'.\n",
    "A line after\n",
    "Another line after\n",
    "The last line with missing eol",
  ]

  svntest.main.file_write(iota_path, ''.join(iota_contents))
  expected_output = svntest.wc.State(wc_dir, {
    'iota'  : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)
  unidiff_patch = [
    "--- iota\t(revision 1)\n",
    "+++ iota\t(working copy)\n",
    "@@ -1,7 +1,7 @@\n",
    " One line\n",
    " Another line\n",
    " A third line \n",
    "-This is the file 'iota'.\n",
    "+It is the file 'iota'.\n",
    " A line after\n",
    " Another line after\n",
    " The last line with missing eol\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  iota_contents = [
    "One line\n",
    "Another line\n",
    "A third line \n",
    "It is the file 'iota'.\n",
    "A line after\n",
    "Another line after\n",
    "The last line with missing eol\n",
  ]
  expected_output = [
    'U         %s\n' % sbox.ospath('iota'),
  ]

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('iota', contents=''.join(iota_contents))

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

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_with_properties(sbox):
  "patch with properties"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  iota_path = sbox.ospath('iota')

  modified_prop_contents = "This is the property 'modified'.\n"
  deleted_prop_contents = "This is the property 'deleted'.\n"

  # Set iota prop contents
  svntest.main.run_svn(None, 'propset', 'modified', modified_prop_contents,
                       iota_path)
  svntest.main.run_svn(None, 'propset', 'deleted', deleted_prop_contents,
                       iota_path)
  expected_output = svntest.wc.State(wc_dir, {
      'iota'    : Item(verb='Sending'),
      })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)
  # Apply patch

  unidiff_patch = [
    "Index: iota\n",
    "===================================================================\n",
    "--- iota\t(revision 1)\n",
    "+++ iota\t(working copy)\n",
    "Property changes on: iota\n",
    "-------------------------------------------------------------------\n",
    "Modified: modified\n",
    "## -1 +1 ##\n",
    "-This is the property 'modified'.\n",
    "+The property 'modified' has changed.\n",
    "Added: added\n",
    "## -0,0 +1 ##\n",
    "+This is the property 'added'.\n",
    "Deleted: deleted\n",
    "## -1 +0,0 ##\n",
    "-This is the property 'deleted'.\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch), 'wb')

  modified_prop_contents = "The property 'modified' has changed.\n"
  added_prop_contents = "This is the property 'added'.\n"

  expected_output = [
    ' U        %s\n' % sbox.ospath('iota'),
  ]

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('iota', props={'modified' : modified_prop_contents,
                                     'added' : added_prop_contents})
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', status=' M', wc_rev='2')

  expected_skip = wc.State(wc_dir, { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       [], True, True)
  # And repeat
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(status=' G')
  })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       [], True, True)

  # Reverse
  expected_output.tweak('iota', status=' U')
  expected_status.tweak('iota', status='  ')
  expected_disk.tweak('iota',
                      props={'deleted': "This is the property 'deleted'.\n",
                             'modified': "This is the property 'modified'.\n"})
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

  # Repeat
  expected_output.tweak('iota', status=' G')
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

  # And now try against a not existing target
  svntest.actions.run_and_verify_svn(None, [],
                                     'rm', '--force', sbox.ospath('iota'))
  expected_output.remove('iota')
  expected_disk.remove('iota')
  expected_status.tweak('iota', status='D ')
  expected_skip.add({
    'iota' : Item(verb='Skipped'),
  })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       [], True, True)


def patch_same_twice(sbox):
  "apply the same patch twice"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  mu_path = sbox.ospath('A/mu')
  beta_path = sbox.ospath('A/B/E/beta')

  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "\n",
    "Again, we wish to congratulate you over your email success in our\n"
    "computer Balloting.\n"
  ]

  # Set mu contents
  svntest.main.file_write(mu_path, ''.join(mu_contents))
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu'       : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)

  # Apply patch

  unidiff_patch = [
    "Index: A/D/gamma\n",
    "===================================================================\n",
    "--- A/D/gamma\t(revision 1)\n",
    "+++ A/D/gamma\t(working copy)\n",
    "@@ -1 +1 @@\n",
    "-This is the file 'gamma'.\n",
    "+It is the file 'gamma'.\n",
    "Index: iota\n",
    "===================================================================\n",
    "--- iota\t(revision 1)\n",
    "+++ iota\t(working copy)\n",
    "@@ -1 +1,2 @@\n",
    " This is the file 'iota'.\n",
    "+Some more bytes\n",
    "\n",
    "Index: new\n",
    "===================================================================\n",
    "--- new	(revision 0)\n",
    "+++ new	(revision 0)\n",
    "@@ -0,0 +1 @@\n",
    "+new\n",
    "\n",
    "--- A/mu.orig	2009-06-24 15:23:55.000000000 +0100\n",
    "+++ A/mu	2009-06-24 15:21:23.000000000 +0100\n",
    "@@ -6,6 +6,9 @@\n",
    " through a computer ballot system drawn from over 100,000 company\n",
    " and 50,000,000 individual email addresses from all over the world.\n",
    " \n",
    "+It is a promotional program aimed at encouraging internet users;\n",
    "+therefore you do not need to buy ticket to enter for it.\n",
    "+\n",
    " Your email address drew and have won the sum of  750,000 Euros\n",
    " ( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    " file with\n",
    "@@ -14,11 +17,8 @@\n",
    "     BATCH NUMBERS :\n",
    "     EULO/1007/444/606/08;\n",
    "     SERIAL NUMBER: 45327\n",
    "-and PROMOTION DATE: 13th June. 2009\n",
    "+and PROMOTION DATE: 14th June. 2009\n",
    " \n",
    " To claim your winning prize, you are to contact the appointed\n",
    " agent below as soon as possible for the immediate release of your\n",
    " winnings with the below details.\n",
    "-\n",
    "-Again, we wish to congratulate you over your email success in our\n",
    "-computer Balloting.\n",
    "Index: A/B/E/beta\n",
    "===================================================================\n",
    "--- A/B/E/beta	(revision 1)\n",
    "+++ A/B/E/beta	(working copy)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'beta'.\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  gamma_contents = "It is the file 'gamma'.\n"
  iota_contents = "This is the file 'iota'.\nSome more bytes\n"
  new_contents = "new\n"
  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "It is a promotional program aimed at encouraging internet users;\n",
    "therefore you do not need to buy ticket to enter for it.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 14th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
  ]

  expected_output = [
    'U         %s\n' % sbox.ospath('A/D/gamma'),
    'U         %s\n' % sbox.ospath('iota'),
    'A         %s\n' % sbox.ospath('new'),
    'U         %s\n' % sbox.ospath('A/mu'),
    'D         %s\n' % beta_path,
  ]

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/D/gamma', contents=gamma_contents)
  expected_disk.tweak('iota', contents=iota_contents)
  expected_disk.add({'new' : Item(contents=new_contents)})
  expected_disk.tweak('A/mu', contents=''.join(mu_contents))
  expected_disk.remove('A/B/E/beta')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/gamma', status='M ')
  expected_status.tweak('iota', status='M ')
  expected_status.add({'new' : Item(status='A ', wc_rev=0)})
  expected_status.tweak('A/mu', status='M ', wc_rev=2)
  expected_status.tweak('A/B/E/beta', status='D ')

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run
  # apply the patch again
  expected_output = [
    'G         %s\n' % sbox.ospath('A/D/gamma'),
    '>         hunk @@ -1,1 +1,1 @@ already applied\n',
    'G         %s\n' % sbox.ospath('iota'),
    # The iota patch inserts a line after the first line in the file,
    # with no trailing context. Originally, Subversion applied this patch
    # multiple times, which matched the behaviour of Larry Wall's patch
    # implementation.
    '>         hunk @@ -1,1 +1,2 @@ already applied\n',
    'G         %s\n' % sbox.ospath('new'),
    '>         hunk @@ -0,0 +1,1 @@ already applied\n',
    'G         %s\n' % sbox.ospath('A/mu'),
    '>         hunk @@ -6,6 +6,9 @@ already applied\n',
    '>         hunk @@ -14,11 +17,8 @@ already applied\n',
    'G         %s\n' % sbox.ospath('A/B/E/beta'),
    '>         hunk @@ -1,1 +0,0 @@ already applied\n',
  ]

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_dir_properties(sbox):
  "patch with dir properties"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  B_path = sbox.ospath('A/B')

  modified_prop_contents = "This is the property 'modified'.\n"
  deleted_prop_contents = "This is the property 'deleted'.\n"

  # Set the properties
  svntest.main.run_svn(None, 'propset', 'modified', modified_prop_contents,
                       wc_dir)
  svntest.main.run_svn(None, 'propset', 'deleted', deleted_prop_contents,
                       B_path)
  expected_output = svntest.wc.State(wc_dir, {
      '.'    : Item(verb='Sending'),
      'A/B'    : Item(verb='Sending'),
      })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('', wc_rev=2)
  expected_status.tweak('A/B', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)
  # Apply patch

  unidiff_patch = [
    "Index: .\n",
    "===================================================================\n",
    "--- .\t(revision 1)\n",
    "+++ .\t(working copy)\n",
    "\n",
    "Property changes on: .\n",
    "-------------------------------------------------------------------\n",
    "Modified: modified\n",
    "## -1 +1 ##\n",
    "-This is the property 'modified'.\n",
    "+The property 'modified' has changed.\n",
    "Added: svn:ignore\n",
    "## -0,0 +1,3 ##\n",
    "+*.o\n",
    "+.libs\n",
    "+*.lo\n",
    "Index: A/B\n",
    "===================================================================\n",
    "--- A/B\t(revision 1)\n",
    "+++ A/B\t(working copy)\n",
    "\n",
    "Property changes on: A/B\n",
    "-------------------------------------------------------------------\n",
    "Deleted: deleted\n",
    "## -1 +0,0 ##\n",
    "-This is the property 'deleted'.\n",
    "Added: svn:executable\n",
    "## -0,0 +1 ##\n",
    "+*\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  modified_prop_contents = "The property 'modified' has changed.\n"
  ignore_prop_contents = "*.o\n.libs\n*.lo\n"

  expected_output = [
    ' U        %s\n' % wc_dir,
    ' C        %s\n' % sbox.ospath('A/B'),
    '>         rejected hunk ## -0,0 +1,1 ## (svn:executable)\n',
  ] + svntest.main.summary_of_conflicts(prop_conflicts=1)

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    '' : Item(props={'modified' : modified_prop_contents,
                     'svn:ignore' : ignore_prop_contents}),
    'A/B.svnpatch.rej'  : Item(contents="--- A/B\n+++ A/B\n" +
                               "Property: svn:executable\n" +
                               "## -0,0 +1,1 ##\n+*\n"),
    })
  expected_disk.tweak('A/B', props={})
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('', status=' M', wc_rev=2)
  expected_status.tweak('A/B', status=' M', wc_rev=2)

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_add_path_with_props(sbox):
  "patch that adds paths with props"

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

  patch_file_path = sbox.get_tempname('my.patch')
  iota_path = sbox.ospath('iota')

  # Apply patch that adds two files, one of which is empty.
  # Both files have properties.

  unidiff_patch = [
    "Index: new\n",
    "===================================================================\n",
    "--- new\t(revision 0)\n",
    "+++ new\t(working copy)\n",
    "@@ -0,0 +1 @@\n",
    "+This is the file 'new'\n",
    "\n",
    "Property changes on: new\n",
    "-------------------------------------------------------------------\n",
    "Added: added\n",
    "## -0,0 +1 ##\n",
    "+This is the property 'added'.\n",
    "Index: X\n",
    "===================================================================\n",
    "--- X\t(revision 0)\n",
    "+++ X\t(working copy)\n",
    "\n",
    "Property changes on: X\n",
    "-------------------------------------------------------------------\n",
    "Added: added\n",
    "## -0,0 +1 ##\n",
    "+This is the property 'added'.\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  added_prop_contents = "This is the property 'added'.\n"

  expected_output = [
    'A         %s\n' % sbox.ospath('new'),
    'A         %s\n' % sbox.ospath('X'),
  ]

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({'new': Item(contents="This is the file 'new'\n",
                                 props={'added' : added_prop_contents})})
  expected_disk.add({'X': Item(contents="",
                               props={'added' : added_prop_contents})})
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({'new': Item(status='A ', wc_rev='0')})
  expected_status.add({'X': Item(status='A ', wc_rev='0')})

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_prop_offset(sbox):
  "property patch with offset searching"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = os.path.abspath(sbox.get_tempname('my.patch'))
  iota_path = sbox.ospath('iota')

  prop1_content = ''.join([
    "Dear internet user,\n",
    # The missing line here will cause the first hunk to match early
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "\n",
    "Again, we wish to congratulate you over your email success in our\n"
    "computer Balloting.\n",
  ])

  # prop2's content will make both a late and early match possible.
  # The hunk to be applied is replicated here for reference:
  # ## -5,6 +5,7 ##
  #  property
  #  property
  #  property
  # +x
  #  property
  #  property
  #  property
  #
  # This hunk wants to be applied at line 5, but that isn't
  # possible because line 8 ("zzz") does not match "property".
  # The early match happens at line 2 (offset 3 = 5 - 2).
  # The late match happens at line 9 (offset 4 = 9 - 5).
  # Subversion will pick the early match in this case because it
  # is closer to line 5.
  prop2_content = ''.join([
    "property\n",
    "property\n",
    "property\n",
    "property\n",
    "property\n",
    "property\n",
    "property\n",
    "zzz\n",
    "property\n",
    "property\n",
    "property\n",
    "property\n",
    "property\n",
    "property\n",
    "property\n"
  ])

  # Set iota prop contents
  svntest.main.run_svn(None, 'propset', 'prop1', prop1_content,
                       iota_path)
  svntest.main.run_svn(None, 'propset', 'prop2', prop2_content,
                       iota_path)
  expected_output = svntest.wc.State(wc_dir, {
    'iota'       : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)

  # Apply patch

  unidiff_patch = [
    "Index: iota\n",
    "===================================================================\n",
    "--- iota	(revision XYZ)\n",
    "+++ iota	(working copy)\n",
    "\n",
    "Property changes on: iota\n",
    "-------------------------------------------------------------------\n",
    "Modified: prop1\n",
    "## -6,6 +6,9 ##\n",
    " through a computer ballot system drawn from over 100,000 company\n",
    " and 50,000,000 individual email addresses from all over the world.\n",
    " \n",
    "+It is a promotional program aimed at encouraging internet users;\n",
    "+therefore you do not need to buy ticket to enter for it.\n",
    "+\n",
    " Your email address drew and have won the sum of  750,000 Euros\n",
    " ( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    " file with\n",
    "## -14,11 +17,8 ##\n",
    "     BATCH NUMBERS :\n",
    "     EULO/1007/444/606/08;\n",
    "     SERIAL NUMBER: 45327\n",
    "-and PROMOTION DATE: 13th June. 2009\n",
    "+and PROMOTION DATE: 14th June. 2009\n",
    " \n",
    " To claim your winning prize, you are to contact the appointed\n",
    " agent below as soon as possible for the immediate release of your\n",
    " winnings with the below details.\n",
    "-\n",
    "-Again, we wish to congratulate you over your email success in our\n",
    "-computer Balloting.\n",
    "Modified: prop2\n",
    "## -5,6 +5,7 ##\n",
    " property\n",
    " property\n",
    " property\n",
    "+x\n",
    " property\n",
    " property\n",
    " property\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  prop1_content = ''.join([
    "Dear internet user,\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "It is a promotional program aimed at encouraging internet users;\n",
    "therefore you do not need to buy ticket to enter for it.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "These extra lines will cause the second hunk to match late\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 14th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
  ])

  prop2_content = ''.join([
    "property\n",
    "property\n",
    "property\n",
    "property\n",
    "x\n",
    "property\n",
    "property\n",
    "property\n",
    "zzz\n",
    "property\n",
    "property\n",
    "property\n",
    "property\n",
    "property\n",
    "property\n",
    "property\n",
  ])

  os.chdir(wc_dir)

  # Changing two properties so output order not well defined.
  expected_output = svntest.verify.UnorderedOutput([
    ' U        iota\n',
    '>         applied hunk ## -6,6 +6,9 ## with offset -1 (prop1)\n',
    '>         applied hunk ## -14,11 +17,8 ## with offset 4 (prop1)\n',
    '>         applied hunk ## -5,6 +5,7 ## with offset -3 (prop2)\n',
  ])

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('iota', props = {'prop1' : prop1_content,
                                       'prop2' : prop2_content})

  expected_status = svntest.actions.get_virginal_state('', 1)
  expected_status.tweak('iota', status=' M', wc_rev=2)

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch('', patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_prop_with_fuzz(sbox):
  "property patch with fuzz"

  sbox.build()
  wc_dir = sbox.wc_dir
  patch_file_path = sbox.get_tempname('my.patch')

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

  # We have replaced a couple of lines to cause fuzz. Those lines contains
  # the word fuzz
  prop_contents = ''.join([
    "Line replaced for fuzz = 1\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "Line replaced for fuzz = 2 with only the second context line changed\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "This line is inserted to cause an offset of +1\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "\n",
    "Line replaced for fuzz = 2\n",
    "Line replaced for fuzz = 2\n",
  ])

  # Set mu prop contents
  svntest.main.run_svn(None, 'propset', 'prop', prop_contents,
                       mu_path)
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu'       : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                      expected_status)

  unidiff_patch = [
    "Index: mu\n",
    "===================================================================\n",
    "--- A/mu\t(revision 0)\n",
    "+++ A/mu\t(revision 0)\n",
    "\n",
    "Property changes on: mu\n",
    "Modified: prop\n",
    "## -1,6 +1,7 ##\n",
    " Dear internet user,\n",
    " \n",
    " We wish to congratulate you over your email success in our computer\n",
    "+A new line here\n",
    " Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    " in which email addresses were used. All participants were selected\n",
    " through a computer ballot system drawn from over 100,000 company\n",
    "## -7,7 +8,9 ##\n",
    " and 50,000,000 individual email addresses from all over the world.\n",
    " \n",
    " Your email address drew and have won the sum of  750,000 Euros\n",
    "+Another new line\n",
    " ( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "+A third new line\n",
    " file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "## -19,6 +20,7 ##\n",
    " To claim your winning prize, you are to contact the appointed\n",
    " agent below as soon as possible for the immediate release of your\n",
    " winnings with the below details.\n",
    "+A fourth new line\n",
    " \n",
    " Again, we wish to congratulate you over your email success in our\n"
    " computer Balloting. [No trailing newline here]"
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  prop_contents = ''.join([
    "Line replaced for fuzz = 1\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "A new line here\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "Line replaced for fuzz = 2 with only the second context line changed\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "Another new line\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "A third new line\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "This line is inserted to cause an offset of +1\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "A fourth new line\n",
    "\n",
    "Line replaced for fuzz = 2\n",
    "Line replaced for fuzz = 2\n",
  ])

  expected_output = [
    ' U        %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk ## -1,6 +1,7 ## with fuzz 1 (prop)\n',
    '>         applied hunk ## -7,7 +8,9 ## with fuzz 2 (prop)\n',
    '>         applied hunk ## -19,6 +20,7 ## with offset 1 and fuzz 2 (prop)\n',
  ]
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/mu', props = {'prop' : prop_contents})

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status=' M', wc_rev=2)

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_git_empty_files(sbox):
  "patch that contains empty files"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir
  patch_file_path = sbox.get_tempname('my.patch')

  new_path = sbox.ospath('new')

  unidiff_patch = [
    "Index: new\n",
    "===================================================================\n",
    "diff --git a/new b/new\n",
    "new file mode 100644\n",
    "Index: iota\n",
    "===================================================================\n",
    "diff --git a/iota b/iota\n",
    "deleted file mode 100644\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  expected_output = [
    'A         %s\n' % sbox.ospath('new'),
    'D         %s\n' % sbox.ospath('iota'),
  ]
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({'new' : Item(contents="")})
  expected_disk.remove('iota')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({'new' : Item(status='A ', wc_rev=0)})
  expected_status.tweak('iota', status='D ')

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_old_target_names(sbox):
  "patch using old target names"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  mu_path = sbox.ospath('A/mu')

  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "\n",
    "Again, we wish to congratulate you over your email success in our\n"
    "computer Balloting.\n"
  ]

  # Set mu contents
  svntest.main.file_write(mu_path, ''.join(mu_contents))
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu'       : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)

  # Apply patch

  unidiff_patch = [
    "--- A/mu	2009-06-24 15:23:55.000000000 +0100\n",
    "+++ A/mu.new	2009-06-24 15:21:23.000000000 +0100\n",
    "@@ -6,6 +6,9 @@\n",
    " through a computer ballot system drawn from over 100,000 company\n",
    " and 50,000,000 individual email addresses from all over the world.\n",
    " \n",
    "+It is a promotional program aimed at encouraging internet users;\n",
    "+therefore you do not need to buy ticket to enter for it.\n",
    "+\n",
    " Your email address drew and have won the sum of  750,000 Euros\n",
    " ( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    " file with\n",
    "@@ -14,11 +17,8 @@\n",
    "     BATCH NUMBERS :\n",
    "     EULO/1007/444/606/08;\n",
    "     SERIAL NUMBER: 45327\n",
    "-and PROMOTION DATE: 13th June. 2009\n",
    "+and PROMOTION DATE: 14th June. 2009\n",
    " \n",
    " To claim your winning prize, you are to contact the appointed\n",
    " agent below as soon as possible for the immediate release of your\n",
    " winnings with the below details.\n",
    "-\n",
    "-Again, we wish to congratulate you over your email success in our\n",
    "-computer Balloting.\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "It is a promotional program aimed at encouraging internet users;\n",
    "therefore you do not need to buy ticket to enter for it.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 14th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
  ]

  expected_output = [
    'U         %s\n' % sbox.ospath('A/mu'),
  ]

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

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='M ', wc_rev=2)

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_reverse_revert(sbox):
  "revert a patch by reverse patching"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  mu_path = sbox.ospath('A/mu')

  mu_contents_pre_patch = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "\n",
    "Again, we wish to congratulate you over your email success in our\n"
    "computer Balloting.\n"
  ]

  # Set mu contents
  svntest.main.file_write(mu_path, ''.join(mu_contents_pre_patch), 'wb')
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu'       : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)

  # Apply patch

  unidiff_patch = [
    "Index: A/D/gamma\n",
    "===================================================================\n",
    "--- A/D/gamma\t(revision 1)\n",
    "+++ A/D/gamma\t(working copy)\n",
    "@@ -1 +1 @@\n",
    "-This is the file 'gamma'.\n",
    "+It is the file 'gamma'.\n",
    "Index: iota\n",
    "===================================================================\n",
    "--- iota\t(revision 1)\n",
    "+++ iota\t(working copy)\n",
    "@@ -1 +1,2 @@\n",
    " This is the file 'iota'.\n",
    "+Some more bytes\n",
    "\n",
    "Index: new\n",
    "===================================================================\n",
    "--- new	(revision 0)\n",
    "+++ new	(revision 0)\n",
    "@@ -0,0 +1 @@\n",
    "+new\n",
    "\n",
    "--- A/mu.orig	2009-06-24 15:23:55.000000000 +0100\n",
    "+++ A/mu	2009-06-24 15:21:23.000000000 +0100\n",
    "@@ -6,6 +6,9 @@\n",
    " through a computer ballot system drawn from over 100,000 company\n",
    " and 50,000,000 individual email addresses from all over the world.\n",
    " \n",
    "+It is a promotional program aimed at encouraging internet users;\n",
    "+therefore you do not need to buy ticket to enter for it.\n",
    "+\n",
    " Your email address drew and have won the sum of  750,000 Euros\n",
    " ( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    " file with\n",
    "@@ -14,11 +17,8 @@\n",
    "     BATCH NUMBERS :\n",
    "     EULO/1007/444/606/08;\n",
    "     SERIAL NUMBER: 45327\n",
    "-and PROMOTION DATE: 13th June. 2009\n",
    "+and PROMOTION DATE: 14th June. 2009\n",
    " \n",
    " To claim your winning prize, you are to contact the appointed\n",
    " agent below as soon as possible for the immediate release of your\n",
    " winnings with the below details.\n",
    "-\n",
    "-Again, we wish to congratulate you over your email success in our\n",
    "-computer Balloting.\n",
    "Index: A/B/E/beta\n",
    "===================================================================\n",
    "--- A/B/E/beta	(revision 1)\n",
    "+++ A/B/E/beta	(working copy)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'beta'.\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch), 'wb')

  gamma_contents = "It is the file 'gamma'.\n"
  iota_contents = "This is the file 'iota'.\nSome more bytes\n"
  new_contents = "new\n"
  mu_contents_post_patch = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "It is a promotional program aimed at encouraging internet users;\n",
    "therefore you do not need to buy ticket to enter for it.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 14th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
  ]

  expected_output = wc.State(wc_dir, {
    'A/D/gamma'   : Item(status='U '),
    'iota'        : Item(status='U '),
    'new'         : Item(status='A '),
    'A/mu'        : Item(status='U '),
    'A/B/E/beta'  : Item(status='D '),
  })

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/D/gamma', contents=gamma_contents)
  expected_disk.tweak('iota', contents=iota_contents)
  expected_disk.add({'new' : Item(contents=new_contents)})
  expected_disk.tweak('A/mu', contents=''.join(mu_contents_post_patch))
  expected_disk.remove('A/B/E/beta')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/D/gamma', status='M ')
  expected_status.tweak('iota', status='M ')
  expected_status.add({'new' : Item(status='A ', wc_rev=0)})
  expected_status.tweak('A/mu', status='M ', wc_rev=2)
  expected_status.tweak('A/B/E/beta', status='D ')

  expected_skip = wc.State(wc_dir, { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       [], True, True)

  # Try again
  expected_output.tweak(status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       [], True, True)

  # Applying the same patch in reverse should undo local mods
  expected_output = wc.State(wc_dir, {
    'A/D/gamma'   : Item(status='U '),
    'iota'        : Item(status='U '),
    'new'         : Item(status='D '),
    'A/mu'        : Item(status='U '),
    'A/B/E/beta'  : Item(status='A '),
  })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/mu', contents=''.join(mu_contents_pre_patch))

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)

  ### svn patch should check whether the deleted file has the same
  ### content as the file added by the patch and revert the deletion
  ### instead of causing a replacement.
  expected_status.tweak('A/B/E/beta', status='R ')

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

  # And again
  expected_output.tweak(status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

def patch_one_property(sbox, trailing_eol):
  """Helper.  Apply a patch that sets the property 'k' to 'v\n' or to 'v',
   and check the results."""

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

  patch_file_path = sbox.get_tempname('my.patch')
  mu_path = sbox.ospath('A/mu')

  # Apply patch

  unidiff_patch = [
    "Index: .\n",
    "===================================================================\n",
    "diff --git a/subversion/branches/1.6.x b/subversion/branches/1.6.x\n",
    "--- a/subversion/branches/1.6.x\t(revision 1033278)\n",
    "+++ b/subversion/branches/1.6.x\t(working copy)\n",
    "\n",
    "Property changes on: subversion/branches/1.6.x\n",
    "___________________________________________________________________\n",
    "Modified: svn:mergeinfo\n",
    "   Merged /subversion/trunk:r964349\n",
    "Added: k\n",
    "## -0,0 +1 ##\n",
    "+v\n",
  ]

  if trailing_eol:
    value = "v\n"
  else:
    value = "v"
    unidiff_patch += ['\ No newline at end of property\n']

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch), 'wb')

  expected_output = [
    ' U        %s\n' % os.path.join(wc_dir),
  ]

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({'': Item(props={'k' : value})})

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('', status=' M')

  expected_skip = wc.State('.', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1, # dry-run
                                       '--strip', '3')

  svntest.actions.check_prop('k', wc_dir, [value.encode()])

def patch_strip_cwd(sbox):
  "patch --strip propchanges cwd"
  return patch_one_property(sbox, True)

@Issue(3814)
def patch_set_prop_no_eol(sbox):
  "patch doesn't append newline to properties"
  return patch_one_property(sbox, False)

# Regression test for issue #3697
@Issue(3697)
def patch_add_symlink(sbox):
  "patch that adds a symlink"

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

  patch_file_path = sbox.get_tempname('my.patch')

  # Apply patch

  unidiff_patch = [
    "Index: iota_symlink\n",
    "===================================================================\n",
    "--- iota_symlink\t(revision 0)\n",
    "+++ iota_symlink\t(working copy)\n",
    "@@ -0,0 +1 @@\n",
    "+link iota\n",
    "\\ No newline at end of file\n"
    "\n",
    "Property changes on: iota_symlink\n",
    "-------------------------------------------------------------------\n",
    "Added: svn:special\n",
    "## -0,0 +1 ##\n",
    "+*\n",
    "\\ No newline at end of property\n"
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

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

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({'iota_symlink': Item(contents="This is the file 'iota'.\n",
                                          props={'svn:special' : '*'})})
  if not svntest.main.is_posix_os():
    expected_disk.tweak('iota_symlink', contents='link iota')
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({'iota_symlink': Item(status='A ', wc_rev='0')})

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       [], True, True)

  # And again
  expected_output.tweak('iota_symlink', status='GG')
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       [], True, True)

  # Reverse
  expected_output.tweak('iota_symlink', status='D ')
  expected_disk.remove('iota_symlink')
  expected_status.remove('iota_symlink')
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

  # And again
  expected_output.tweak('iota_symlink', status='GG')
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

def patch_moved_away(sbox):
  "patch a file that was moved away"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  mu_path = sbox.ospath('A/mu')

  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 13th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
    "\n",
    "Again, we wish to congratulate you over your email success in our\n"
    "computer Balloting.\n"
  ]

  # Set mu contents
  svntest.main.file_write(mu_path, ''.join(mu_contents))
  expected_output = svntest.wc.State(wc_dir, {
    'A/mu'       : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)

  # Move mu away
  sbox.simple_move("A/mu", "A/mu2")

  # Apply patch
  unidiff_patch = [
    "--- A/mu.orig	2009-06-24 15:23:55.000000000 +0100\n",
    "+++ A/mu	2009-06-24 15:21:23.000000000 +0100\n",
    "@@ -6,6 +6,9 @@\n",
    " through a computer ballot system drawn from over 100,000 company\n",
    " and 50,000,000 individual email addresses from all over the world.\n",
    " \n",
    "+It is a promotional program aimed at encouraging internet users;\n",
    "+therefore you do not need to buy ticket to enter for it.\n",
    "+\n",
    " Your email address drew and have won the sum of  750,000 Euros\n",
    " ( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    " file with\n",
    "@@ -14,11 +17,8 @@\n",
    "     BATCH NUMBERS :\n",
    "     EULO/1007/444/606/08;\n",
    "     SERIAL NUMBER: 45327\n",
    "-and PROMOTION DATE: 13th June. 2009\n",
    "+and PROMOTION DATE: 14th June. 2009\n",
    " \n",
    " To claim your winning prize, you are to contact the appointed\n",
    " agent below as soon as possible for the immediate release of your\n",
    " winnings with the below details.\n",
    "-\n",
    "-Again, we wish to congratulate you over your email success in our\n",
    "-computer Balloting.\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  mu_contents = [
    "Dear internet user,\n",
    "\n",
    "We wish to congratulate you over your email success in our computer\n",
    "Balloting. This is a Millennium Scientific Electronic Computer Draw\n",
    "in which email addresses were used. All participants were selected\n",
    "through a computer ballot system drawn from over 100,000 company\n",
    "and 50,000,000 individual email addresses from all over the world.\n",
    "\n",
    "It is a promotional program aimed at encouraging internet users;\n",
    "therefore you do not need to buy ticket to enter for it.\n",
    "\n",
    "Your email address drew and have won the sum of  750,000 Euros\n",
    "( Seven Hundred and Fifty Thousand Euros) in cash credited to\n",
    "file with\n",
    "    REFERENCE NUMBER: ESP/WIN/008/05/10/MA;\n",
    "    WINNING NUMBER : 14-17-24-34-37-45-16\n",
    "    BATCH NUMBERS :\n",
    "    EULO/1007/444/606/08;\n",
    "    SERIAL NUMBER: 45327\n",
    "and PROMOTION DATE: 14th June. 2009\n",
    "\n",
    "To claim your winning prize, you are to contact the appointed\n",
    "agent below as soon as possible for the immediate release of your\n",
    "winnings with the below details.\n",
  ]

  expected_output = [
    'U         %s\n' % sbox.ospath('A/mu2'),
  ]

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({'A/mu2': Item(contents=''.join(mu_contents))})
  expected_disk.remove('A/mu')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'A/mu2' : Item(status='A ', copied='+', wc_rev='-', moved_from='A/mu'),
  })

  expected_status.tweak('A/mu', status='D ', wc_rev=2, moved_to='A/mu2')

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

@Issue(3991)
def patch_lacking_trailing_eol(sbox):
  "patch file lacking trailing eol"

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

  patch_file_path = sbox.get_tempname('my.patch')
  iota_path = sbox.ospath('iota')
  mu_path = sbox.ospath('A/mu')

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

  # Apply patch
  unidiff_patch = [
    "Index: iota\n",
    "===================================================================\n",
    "--- iota\t(revision 1)\n",
    "+++ iota\t(working copy)\n",
    # TODO: -1 +1
    "@@ -1 +1,2 @@\n",
    " This is the file 'iota'.\n",
    "+Some more bytes", # No trailing \n on this line!
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  gamma_contents = "It is the file 'gamma'.\n"
  iota_contents = "This is the file 'iota'.\n"
  new_contents = "new\n"

  expected_output = [
    'U         %s\n' % sbox.ospath('iota'),
  ]

  # Expect a newline to be appended
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('iota', contents=iota_contents + "Some more bytes\n")

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', status='M ')

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

@Issue(4003)
def patch_deletes_prop(sbox):
  "patch deletes prop, directly and via reversed add"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  iota_path = sbox.ospath('iota')

  svntest.main.run_svn(None, 'propset', 'propname', 'propvalue',
                       iota_path)
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)

  # Apply patch
  unidiff_patch = [
    "Index: iota\n",
    "===================================================================\n",
    "--- iota\t(revision 1)\n",
    "+++ iota\t(working copy)\n",
    "\n",
    "Property changes on: iota\n",
    "___________________________________________________________________\n",
    "Deleted: propname\n",
    "## -1 +0,0 ##\n",
    "-propvalue\n",
    ]
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  # Expect the original state of the working copy in r1, exception
  # that iota is at r2 now.
  expected_disk = svntest.main.greek_state.copy()
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', status=' M')
  expected_status.tweak('iota', wc_rev=2)
  expected_skip = wc.State('', { })
  expected_output = [
    ' U        %s\n' % sbox.ospath('iota'),
  ]
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

  # Revert any local mods, then try to reverse-apply a patch which
  # *adds* the property.
  svntest.main.run_svn(None, 'revert', iota_path)

  # Apply patch
  unidiff_patch = [
    "Index: iota\n",
    "===================================================================\n",
    "--- iota\t(revision 1)\n",
    "+++ iota\t(working copy)\n",
    "\n",
    "Property changes on: iota\n",
    "___________________________________________________________________\n",
    "Added: propname\n",
    "## -0,0 +1 ##\n",
    "+propvalue\n",
    ]
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1, # dry-run
                                       '--reverse-diff')

@Issue(4004)
def patch_reversed_add_with_props(sbox):
  "reverse patch new file+props atop uncommitted"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir
  patch_file_path = sbox.get_tempname('my.patch')

  # Add a new file which also has props set on it.
  newfile_path = sbox.ospath('newfile')
  newfile_contents = ["This is the file 'newfile'.\n"]
  svntest.main.file_write(newfile_path, ''.join(newfile_contents))
  svntest.main.run_svn(None, 'add', newfile_path)
  svntest.main.run_svn(None, 'propset', 'propname', 'propvalue',
                       newfile_path)

  # Generate a patch file from our current diff (rooted at the working
  # copy root).
  cwd = os.getcwd()
  try:
    os.chdir(wc_dir)
    exit_code, diff_output, err_output = svntest.main.run_svn(None, 'diff')
  finally:
    os.chdir(cwd)
  svntest.main.file_write(patch_file_path, ''.join(diff_output))

  # Okay, now commit up.
  expected_output = svntest.wc.State(wc_dir, {
    'newfile' : Item(verb='Adding'),
    })

  # Now, we'll try to reverse-apply the very diff we just created.  We
  # expect the original state of the working copy in r1.
  expected_disk = svntest.main.greek_state.copy()
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_skip = wc.State('', { })
  expected_output = [
    'D         %s\n' % newfile_path,
  ]
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1, # dry-run
                                       '--reverse-diff')

@Issue(4004)
def patch_reversed_add_with_props2(sbox):
  "reverse patch new file+props"

  sbox.build()
  wc_dir = sbox.wc_dir
  patch_file_path = sbox.get_tempname('my.patch')

  # Add a new file which also has props set on it.
  newfile_path = sbox.ospath('newfile')
  newfile_contents = ["This is the file 'newfile'.\n"]
  svntest.main.file_write(newfile_path, ''.join(newfile_contents))
  svntest.main.run_svn(None, 'add', newfile_path)
  svntest.main.run_svn(None, 'propset', 'propname', 'propvalue',
                       newfile_path)

  # Generate a patch file from our current diff (rooted at the working
  # copy root).
  cwd = os.getcwd()
  try:
    os.chdir(wc_dir)
    exit_code, diff_output, err_output = svntest.main.run_svn(None, 'diff')
  finally:
    os.chdir(cwd)
  svntest.main.file_write(patch_file_path, ''.join(diff_output))

  # Okay, now commit up.
  expected_output = svntest.wc.State(wc_dir, {
    'newfile' : Item(verb='Adding'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({'newfile' : Item(wc_rev=2, status='  ')})
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)

  # Now, we'll try to reverse-apply the very diff we just created.  We
  # expect the original state of the working copy in r1 plus 'newfile'
  # scheduled for deletion.
  expected_disk = svntest.main.greek_state.copy()
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({'newfile' : Item(status='D ', wc_rev=2)})
  expected_skip = wc.State('', { })
  expected_output = [
    'D         %s\n' % newfile_path,
  ]
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1, # dry-run
                                       '--reverse-diff')

def patch_dev_null(sbox):
  "patch with /dev/null filenames"

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

  patch_file_path = sbox.get_tempname('my.patch')

  # Git (and maybe other tools) use '/dev/null' as the old path for
  # newly added files, and as the new path for deleted files.
  # The path selection algorithm in 'svn patch' must detect this and
  # avoid using '/dev/null' as a patch target.
  unidiff_patch = [
    "Index: new\n",
    "===================================================================\n",
    "--- /dev/null\n",
    "+++ new	(revision 0)\n",
    "@@ -0,0 +1 @@\n",
    "+new\n",
    "\n",
    "Index: A/B/E/beta\n",
    "===================================================================\n",
    "--- A/B/E/beta	(revision 1)\n",
    "+++ /dev/null\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'beta'.\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  new_contents = "new\n"
  expected_output = [
    'A         %s\n' % sbox.ospath('new'),
    'D         %s\n' % sbox.ospath('A/B/E/beta'),
  ]

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({'new' : Item(contents=new_contents)})
  expected_disk.remove('A/B/E/beta')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({'new' : Item(status='A ', wc_rev=0)})
  expected_status.tweak('A/B/E/beta', status='D ')

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

@Issue(4049)
def patch_delete_and_skip(sbox):
  "patch that deletes and skips"

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

  patch_file_path = os.path.abspath(sbox.get_tempname('my.patch'))

  os.chdir(wc_dir)

  # We need to use abspaths to trigger the segmentation fault.
  abs = os.path.abspath('.')
  if sys.platform == 'win32':
      abs = abs.replace("\\", "/")

  outside_wc = os.path.join(os.pardir, 'X')
  if sys.platform == 'win32':
      outside_wc = outside_wc.replace("\\", "/")

  unidiff_patch = [
    "Index: %s/A/B/E/alpha\n" % abs,
    "===================================================================\n",
    "--- %s/A/B/E/alpha\t(revision 1)\n" % abs,
    "+++ %s/A/B/E/alpha\t(working copy)\n" % abs,
    "@@ -1 +0,0 @@\n",
    "-This is the file 'alpha'.\n",
    "Index: %s/A/B/E/beta\n" % abs,
    "===================================================================\n",
    "--- %s/A/B/E/beta\t(revision 1)\n" % abs,
    "+++ %s/A/B/E/beta\t(working copy)\n" % abs,
    "@@ -1 +0,0 @@\n",
    "-This is the file 'beta'.\n",
    "Index: %s/A/B/E/out-of-reach\n" % abs,
    "===================================================================\n",
    "--- %s/iota\t(revision 1)\n" % outside_wc,
    "+++ %s/iota\t(working copy)\n" % outside_wc,
    "\n",
    "Property changes on: iota\n",
    "___________________________________________________________________\n",
    "Added: propname\n",
    "## -0,0 +1 ##\n",
    "+propvalue\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  skipped_path = os.path.join(os.pardir, 'X', 'iota')
  expected_output = [
    'D         %s\n' % os.path.join('A', 'B', 'E', 'alpha'),
    'D         %s\n' % os.path.join('A', 'B', 'E', 'beta'),
    'D         %s\n' % os.path.join('A', 'B', 'E'),
    'Skipped missing target: \'%s\'\n' % skipped_path,
  ] + svntest.main.summary_of_conflicts(skipped_paths=1)

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/B/E/alpha')
  expected_disk.remove('A/B/E/beta')
  expected_disk.remove('A/B/E')

  expected_status = svntest.actions.get_virginal_state('', 1)
  expected_status.tweak('A/B/E', status='D ')
  expected_status.tweak('A/B/E/alpha', status='D ')
  expected_status.tweak('A/B/E/beta', status='D ')

  expected_skip = wc.State(
    '',
    {skipped_path: Item(verb='Skipped missing target')})

  svntest.actions.run_and_verify_patch('', patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_target_no_eol_at_eof(sbox):
  "patch target with no eol at eof"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  iota_path = sbox.ospath('iota')
  mu_path = sbox.ospath('A/mu')

  iota_contents = [
    "This is the file iota."
  ]

  mu_contents = [
    "context\n",
    "context\n",
    "context\n",
    "context\n",
    "This is the file mu.\n",
    "context\n",
    "context\n",
    "context\n",
    "context", # no newline at end of file
  ]

  svntest.main.file_write(iota_path, ''.join(iota_contents))
  svntest.main.file_write(mu_path, ''.join(mu_contents))
  expected_output = svntest.wc.State(wc_dir, {
    'iota'  : Item(verb='Sending'),
    'A/mu'  : Item(verb='Sending'),
    })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', wc_rev=2)
  expected_status.tweak('A/mu', wc_rev=2)
  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
                                        expected_status)
  unidiff_patch = [
    "Index: A/mu\n",
    "===================================================================\n",
    "--- A/mu\t(revision 2)\n",
    "+++ A/mu\t(working copy)\n",
    "@@ -2,8 +2,8 @@ context\n",
    " context\n",
    " context\n",
    " context\n",
    "-This is the file mu.\n",
    "+It is really the file mu.\n",
    " context\n",
    " context\n",
    " context\n",
    " context\n",
    "\\ No newline at end of file\n",
    "Index: iota\n",
    "===================================================================\n",
    "--- iota\t(revision 2)\n",
    "+++ iota\t(working copy)\n",
    "@@ -1 +1 @@\n",
    "-This is the file iota.\n",
    "\\ No newline at end of file\n",
    "+It is really the file 'iota'.\n",
    "\\ No newline at end of file\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  iota_contents = [
    "It is really the file 'iota'."
  ]
  mu_contents = [
    "context\n",
    "context\n",
    "context\n",
    "context\n",
    "It is really the file mu.\n",
    "context\n",
    "context\n",
    "context\n",
    "context", # no newline at end of file
  ]
  expected_output = [
    'U         %s\n' % sbox.ospath('A/mu'),
    'U         %s\n' % sbox.ospath('iota'),
  ]

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('iota', contents=''.join(iota_contents))
  expected_disk.tweak('A/mu', contents=''.join(mu_contents))

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', status='M ', wc_rev=2)
  expected_status.tweak('A/mu', status='M ', wc_rev=2)

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

def patch_add_and_delete(sbox):
  "patch add multiple levels and delete"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir
  patch_file_path = sbox.get_tempname('my.patch')

  unidiff_patch = [
    "Index: foo\n",
    "===================================================================\n",
    "--- P/Q/foo\t(revision 0)\n"
    "+++ P/Q/foo\t(working copy)\n"
    "@@ -0,0 +1 @@\n",
    "+This is the file 'foo'.\n",
    "Index: iota\n"
    "===================================================================\n",
    "--- iota\t(revision 1)\n"
    "+++ iota\t(working copy)\n"
    "@@ -1 +0,0 @@\n",
    "-This is the file 'iota'.\n",
    ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  expected_output = [
    'A         %s\n' % sbox.ospath('P'),
    'A         %s\n' % sbox.ospath('P/Q'),
    'A         %s\n' % sbox.ospath('P/Q/foo'),
    'D         %s\n' % sbox.ospath('iota'),
  ]
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('iota')
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_disk.add({'P/Q/foo' : Item(contents="This is the file 'foo'.\n")})
  expected_status.tweak('iota', status='D ')
  expected_status.add({
      'P'       : Item(status='A ', wc_rev=0),
      'P/Q'     : Item(status='A ', wc_rev=0),
      'P/Q/foo' : Item(status='A ', wc_rev=0),
      })
  expected_skip = wc.State('', { })

  # Failed with "The node 'P' was not found" when erroneously checking
  # whether 'P/Q' should be deleted.
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run


def patch_git_with_index_line(sbox):
  "apply git patch with 'index' line"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir
  patch_file_path = sbox.get_tempname('my.patch')

  unidiff_patch = [
    "diff --git a/src/tools/ConsoleRunner/hi.txt b/src/tools/ConsoleRunner/hi.txt\n",
    "new file mode 100644\n",
    "index 0000000..c82a38f\n",
    "--- /dev/null\n",
    "+++ b/src/tools/ConsoleRunner/hi.txt\n",
    "@@ -0,0 +1 @@\n",
    "+hihihihihihi\n",
    "\\ No newline at end of file\n",
  ]

  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  expected_output = [
    'A         %s\n' % sbox.ospath('src'),
    'A         %s\n' % sbox.ospath('src/tools'),
    'A         %s\n' % sbox.ospath('src/tools/ConsoleRunner'),
    'A         %s\n' % sbox.ospath('src/tools/ConsoleRunner/hi.txt'),
  ]

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
      'src'                             : Item(status='A ', wc_rev=0),
      'src/tools'                       : Item(status='A ', wc_rev=0),
      'src/tools/ConsoleRunner'         : Item(status='A ', wc_rev=0),
      'src/tools/ConsoleRunner/hi.txt'  : Item(status='A ', wc_rev=0),
  })

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({'src'                            : Item(),
                     'src/tools'                      : Item(),
                     'src/tools/ConsoleRunner'        : Item(),
                     'src/tools/ConsoleRunner/hi.txt' :
                        Item(contents="hihihihihihi")
                   })

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       1) # dry-run

@Issue(4273)
def patch_change_symlink_target(sbox):
  "patch changes symlink target"

  sbox.build()
  wc_dir = sbox.wc_dir
  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, '\n'.join([
    "Index: link",
    "===================================================================",
    "--- link\t(revision 1)",
    "+++ link\t(working copy)",
    "@@ -1 +1 @@",
    "-link foo",
    "\\ No newline at end of file",
    "+link bardame",
    "\\ No newline at end of file",
    "",
    ]))

  # r2 - Try as plain text with how we encode the symlink
  svntest.main.file_write(sbox.ospath('link'), 'link foo')
  sbox.simple_add('link')

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

  patch_output = [
    'U         %s\n' % sbox.ospath('link'),
  ]

  svntest.actions.run_and_verify_svn(patch_output, [],
                                     'patch', patch_file_path, wc_dir)

  # r3 - Store result
  expected_output = svntest.wc.State(wc_dir, {
    'link'       : Item(verb='Sending'),
  })
  svntest.actions.run_and_verify_commit(wc_dir, expected_output, None)

  # r4 - Now as symlink
  sbox.simple_rm('link')
  sbox.simple_add_symlink('foo', 'link')
  expected_output = svntest.wc.State(wc_dir, {
    'link'       : Item(verb='Replacing'),
  })
  svntest.actions.run_and_verify_commit(wc_dir, expected_output, None)

  svntest.actions.run_and_verify_svn(patch_output, [],
                                     'patch', patch_file_path, wc_dir)

  # TODO: when it passes, verify that the on-disk 'link' is correct ---
  #       symlink to 'bar' (or "link bar" on non-HAVE_SYMLINK platforms)

  # BH: easy check for node type: a non symlink would show as obstructed
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'link'              : Item(status='M ', wc_rev='4'),
  })
  svntest.actions.run_and_verify_status(wc_dir, expected_status)

def patch_replace_dir_with_file_and_vv(sbox):
  "replace dir with file and file with dir"
  sbox.build(read_only=True)
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join([
  # Delete all files in D and descendants to delete D itself
    "Index: A/D/G/pi\n",
    "===================================================================\n",
    "--- A/D/G/pi\t(revision 1)\n",
    "+++ A/D/G/pi\t(working copy)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'pi'.\n",
    "Index: A/D/G/rho\n",
    "===================================================================\n",
    "--- A/D/G/rho\t(revision 1)\n",
    "+++ A/D/G/rho\t(working copy)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'rho'.\n",
    "Index: A/D/G/tau\n",
    "===================================================================\n",
    "--- A/D/G/tau\t(revision 1)\n",
    "+++ A/D/G/tau\t(working copy)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'tau'.\n",
    "Index: A/D/H/chi\n",
    "===================================================================\n",
    "--- A/D/H/chi\t(revision 1)\n",
    "+++ A/D/H/chi\t(working copy)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'chi'.\n",
    "Index: A/D/H/omega\n",
    "===================================================================\n",
    "--- A/D/H/omega\t(revision 1)\n",
    "+++ A/D/H/omega\t(working copy)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'omega'.\n",
    "Index: A/D/H/psi\n",
    "===================================================================\n",
    "--- A/D/H/psi\t(revision 1)\n",
    "+++ A/D/H/psi\t(working copy)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'psi'.\n",
    "Index: A/D/gamma\n",
    "===================================================================\n",
    "--- A/D/gamma\t(revision 1)\n",
    "+++ A/D/gamma\t(working copy)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'gamma'.\n",
  # Delete iota
    "Index: iota\n",
    "===================================================================\n",
    "--- iota\t(revision 1)\n",
    "+++ iota\t(working copy)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'iota'.\n",

  # Add A/D as file
    "Index: A/D\n",
    "===================================================================\n",
    "--- A/D\t(revision 0)\n",
    "+++ A/D\t(working copy)\n",
    "@@ -0,0 +1 @@\n",
    "+New file\n",
    "\\ No newline at end of file\n",

  # Add iota as directory
    "Index: iota\n",
    "===================================================================\n",
    "--- iota\t(revision 1)\n",
    "+++ iota\t(working copy)\n",
    "\n",
    "Property changes on: iota\n",
    "___________________________________________________________________\n",
    "Added: k\n",
    "## -0,0 +1 ##\n",
    "+v\n",
    "\\ No newline at end of property\n",
  ]))

  expected_output = wc.State(wc_dir, {
    'A/D/G/pi'     : Item(status='D '),
    'A/D/G/rho'    : Item(status='D '),
    'A/D/G/tau'    : Item(status='D '),
    'A/D/G'        : Item(status='D '),
    'A/D/H/chi'    : Item(status='D '),
    'A/D/H/omega'  : Item(status='D '),
    'A/D/H/psi'    : Item(status='D '),
    'A/D/H'        : Item(status='D '),
    'A/D/gamma'    : Item(status='D '),
    'A/D'          : Item(status='A ', prev_status='D '),
    'iota'         : Item(status='A ', prev_status='D '),
  })
  expected_skip = wc.State(wc_dir, {})
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.remove('A/D/G/rho', 'A/D/G/pi', 'A/D/G/tau',
                         'A/D/H/psi', 'A/D/H/omega', 'A/D/H/chi',
                         'A/D/gamma', 'A/D/G', 'A/D/H')
  expected_status.tweak('A/D', status='R ')
  expected_status.tweak('iota', status='RM')
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/D/G/rho', 'A/D/G/pi', 'A/D/G/tau',
                       'A/D/H/psi', 'A/D/H/omega', 'A/D/H/chi',
                       'A/D/gamma', 'A/D', 'A/D/G', 'A/D/H')
  expected_disk.add({
    'A/D' : Item(contents="New file"),
    'iota' : Item(contents="", props={u'k': u'v'}),
  })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

@Issue(4297)
def single_line_mismatch(sbox):
  "single line replacement mismatch"

  sbox.build()
  wc_dir = sbox.wc_dir
  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join([
    "Index: test\n",
    "===================================================================\n",
    "--- test\t(revision 1)\n",
    "+++ test\t(working copy)\n",
    "@@ -1 +1 @@\n",
    "-foo\n",
    "\\ No newline at end of file\n",
    "+bar\n",
    "\\ No newline at end of file\n"
    ]))

  # r2 - Try as plain text with how we encode the symlink
  svntest.main.file_write(sbox.ospath('test'), 'line')
  sbox.simple_add('test')
  sbox.simple_commit()

  # And now this patch should fail, as 'line' doesn't equal 'foo'
  # But yet it shows up as deleted instead of conflicted
  expected_output = wc.State(wc_dir, {
    'test' : Item(status='C ')
  })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'test' : Item(status='  ', wc_rev='2'),
  })
  expected_skip = wc.State(wc_dir, {})
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'test'              : Item(contents="line"),
    'test.svnpatch.rej' : Item(contents="--- test\n"
                                        "+++ test\n"
                                        "@@ -1,1 +1,1 @@\n"
                                        "-foo\n"
                                        "\\ No newline at end of file\n"
                                        "+bar\n"
                                        "\\ No newline at end of file\n"),
  })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

@Issue(3644)
def patch_empty_file(sbox):
  "apply a patch to an empty file"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join([
  # patch a file containing just '\n' to 'replacement\n'
    "Index: lf.txt\n",
    "===================================================================\n",
    "--- lf.txt\t(revision 2)\n",
    "+++ lf.txt\t(working copy)\n",
    "@@ -1 +1 @@\n",
    "-\n"
    "+replacement\n",

  # patch a new file 'new.txt\n'
    "Index: new.txt\n",
    "===================================================================\n",
    "--- new.txt\t(revision 0)\n",
    "+++ new.txt\t(working copy)\n",
    "@@ -0,0 +1 @@\n",
    "+new file\n",

  # patch a file containing 0 bytes to 'replacement\n'
    "Index: empty.txt\n",
    "===================================================================\n",
    "--- empty.txt\t(revision 2)\n",
    "+++ empty.txt\t(working copy)\n",
    "@@ -0,0 +1 @@\n",
    "+replacement\n",
  ]))

  sbox.simple_add_text('', 'empty.txt')
  sbox.simple_add_text('\n', 'lf.txt')
  sbox.simple_commit()

  expected_output = [
    'U         %s\n' % sbox.ospath('lf.txt'),
    'A         %s\n' % sbox.ospath('new.txt'),
    'U         %s\n' % sbox.ospath('empty.txt'),
    # Not sure if this line is necessary, but it doesn't hurt
    '>         applied hunk @@ -0,0 +1,1 @@ with offset 0\n',
  ]

  # Current result: lf.txt patched ok, new created, empty succeeds with offset.
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'lf.txt'            : Item(contents="replacement\n"),
    'new.txt'           : Item(contents="new file\n"),
    'empty.txt'         : Item(contents="replacement\n"),
  })
  expected_skip = wc.State(wc_dir, {})
  expected_status = None

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

@Issue(3362)
def patch_apply_no_fuz(sbox):
  "svn diff created patch should apply without fuz"

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

  test1_body = '\n'.join([
      "line_1",
      "line_2",
      "line_3",
      "line_4",
      "line_5",
      "line_6",
      "line_7",
      "line_8",
      "line_9",
      "line_10",
      "line_11",
      "line_12",
      "line_13",
      "line_14",
      "line_15",
      "line_16",
      "line_17",
      "line_18",
      "line_19",
      "line_20",
      "line_21",
      "line_22",
      "line_23",
      "line_24",
      "line_25",
      "line_26",
      "line_27",
      "line_28",
      "line_29",
      "line_30",
      ""
    ])
  svntest.main.file_write(sbox.ospath('test.txt'), test1_body, 'wb')
  test2_body = '\n'.join([
      "line_1a",
      "line_1b",
      "line_1c",
      "line_1",
      "line_2",
      "line_3",
      "line_4",
      "line_5a",
      "line_5b",
      "line_5c",
      "line_6",
      "line_7",
      "line_8",
      "line_9",
      "line_10",
      "line_11a",
      "line_11b",
      "line_11c",
      "line_12",
      "line_13",
      "line_14",
      "line_15",
      "line_16",
      "line_17",
      "line_18",
      "line_19a",
      "line_19b",
      "line_19c",
      "line_20",
      "line_21",
      "line_22",
      "line_23",
      "line_24",
      "line_25",
      "line_26",
      "line_27a",
      "line_27b",
      "line_27c",
      "line_28",
      "line_29",
      "line_30",
      ""
    ])
  svntest.main.file_write(sbox.ospath('test_v2.txt'), test2_body, 'wb')

  sbox.simple_add('test.txt', 'test_v2.txt')

  result, out_text, err_text = svntest.main.run_svn(None,
                                                    'diff',
                                                    '--old',
                                                    sbox.ospath('test.txt'),
                                                    '--new',
                                                    sbox.ospath('test_v2.txt'))

  patch_path = sbox.get_tempname('patch.diff')
  svntest.main.file_write(patch_path, ''.join(out_text), 'wb')

  expected_output = wc.State(wc_dir, {
    'test.txt' : Item(status='U '),
  })

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'test.txt' : Item(contents=test2_body),
    'test_v2.txt' : Item(contents=test2_body),
  })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'test_v2.txt'       : Item(status='A ', wc_rev='-'),
    'test.txt'          : Item(status='A ', wc_rev='-'),
  })

  expected_skip = wc.State(wc_dir, {})

  svntest.actions.run_and_verify_patch(wc_dir, patch_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

@Issue(4315)
def patch_lacking_trailing_eol_on_context(sbox):
  "patch file lacking trailing eol on context"

  # Apply a patch where a hunk (the only hunk, in this case) ends with a
  # context line that has no EOL, where this context line is going to
  # match an existing line that *does* have an EOL.
  #
  # Around trunk@1443700, 'svn patch' wrongly removed an EOL from the
  # target file at that position.

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

  patch_file_path = sbox.get_tempname('my.patch')

  # Prepare
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_disk = svntest.main.greek_state.copy()

  # Prepare the patch
  unidiff_patch = [
    "Index: iota\n",
    "===================================================================\n",
    "--- iota\t(revision 1)\n",
    "+++ iota\t(working copy)\n",
    # TODO: -1 +1
    "@@ -1 +1,2 @@\n",
    "+Some more bytes\n",
    " This is the file 'iota'.", # No trailing \n on this context line!
  ]
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  iota_contents = "This is the file 'iota'.\n"

  expected_output = [ 'U         %s\n' % sbox.ospath('iota') ]

  # Test where the no-EOL context line is the last line in the target.
  expected_disk.tweak('iota', contents="Some more bytes\n" + iota_contents)
  expected_status.tweak('iota', status='M ')
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

  # Test where the no-EOL context line is a non-last line in the target.
  sbox.simple_revert('iota')
  sbox.simple_append('iota', "Another line.\n")
  expected_disk.tweak('iota', contents="Some more bytes\n" + iota_contents +
                                       "Another line.\n")
  expected_output = wc.State(wc_dir, {
    'iota' : Item(status='U ')
  })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

def patch_with_custom_keywords(sbox):
  """patch with custom keywords"""

  sbox.build()
  wc_dir = sbox.wc_dir

  sbox.simple_append('A/mu', '$Qq$\nAB\nZZ\n', truncate=True)
  sbox.simple_propset('svn:keywords', 'Qq=%R', 'A/mu')
  sbox.simple_commit()
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/mu',
                      contents='$Qq: %s $\nAB\nZZ\n' % sbox.repo_url)
  svntest.actions.verify_disk(sbox.wc_dir, expected_disk)

  unidiff_patch = [
    "Index: A/mu\n",
    "===================================================================\n",
    "--- A/mu\t(revision 2)\n",
    "+++ A/mu\t(working copy)\n",
    "@@ -1,3 +1,3 @@\n",
    " $Qq$\n",
    "-AB\n",
    "+ABAB\n",
    " ZZ\n"
    ]

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  expected_output = [ 'U         %s\n' % sbox.ospath('A/mu') ]
  expected_disk.tweak('A/mu',
                      contents='$Qq: %s $\nABAB\nZZ\n' % sbox.repo_url)
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', wc_rev=2)
  expected_status.tweak('A/mu', status='M ')
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

def patch_git_rename(sbox):
  """--git patch with rename header"""

  sbox.build()
  wc_dir = sbox.wc_dir

  # a simple --git rename patch
  unidiff_patch = [
    "diff --git a/iota b/iota2\n",
    "similarity index 100%\n",
    "rename from iota\n",
    "rename to iota2\n",
  ]

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  expected_output = wc.State(wc_dir, {
    'iota'  : Item(status='D '),
    'iota2' : Item(status='A ')
  })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('iota')
  expected_disk.add({'iota2' : Item(contents="This is the file 'iota'.\n")})
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'iota2' : Item(status='A ', copied='+', wc_rev='-', moved_from='iota'),
  })
  expected_status.tweak('iota', status='D ', wc_rev=1, moved_to='iota2')
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # Retry
  expected_output = wc.State(wc_dir, {
    'iota2' : Item(status='G ')
  })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # Reverse
  expected_output = wc.State(wc_dir, {
    'iota2' : Item(status='D '),
    'iota'  : Item(status='A '),
  })
  expected_disk.remove('iota2')
  expected_disk.add({
    'iota'              : Item(contents="This is the file 'iota'.\n"),
  })
  expected_status.remove('iota2')
  expected_status.tweak('iota', moved_to=None, status='  ')
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

  # Retry reverse
  # svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
  #                                      expected_output, expected_disk,
  #                                      expected_status, expected_skip,
  #                                      [], True, True,
  #                                      '--reverse-diff')

@Issue(4533)
def patch_hunk_avoid_reorder(sbox):
  """avoid reordering hunks"""

  sbox.build()
  wc_dir = sbox.wc_dir

  sbox.simple_append('A/mu',
                     'AA\n' 'BB\n' 'CC\n' 'DD\n' 'EE\n' 'FF\n'
                     'TT\n' 'UU\n' 'VV\n' 'WW\n' 'XX\n' 'YY\n'
                     'GG\n' 'HH\n' 'II\n' 'JJ\n' 'KK\n' 'LL\n'
                     '33333\n' '33333\n' '33333\n'
                     '33333\n' '33333\n' '33333\n'
                     '33333\n' '33333\n' '33333\n'
                     '33333\n' '33333\n' '33333\n'
                     'MM\n' 'NN\n' 'OO\n' 'PP\n' 'QQ\n' 'RR\n'
                     'SS\n' 'TT\n' 'UU\n' 'VV\n' 'WW\n' 'XX\n'
                     'YY\n' 'ZZ\n', truncate=True)
  sbox.simple_commit()

  # two hunks, first matches at offset +18, second matches at both -13
  # and +18 but we want the second match as it is after the first
  unidiff_patch = [
    "Index: A/mu\n"
    "===================================================================\n",
    "--- A/mu\t(revision 1)\n",
    "+++ A/mu\t(working copy)\n",
    "@@ -13,6 +13,7 @@\n",
    " MM\n",
    " NN\n",
    " OO\n",
    "+11111\n",
    " PP\n",
    " QQ\n",
    " RR\n",
    "@@ -20,6 +20,7 @@\n",
    " TT\n",
    " UU\n",
    " VV\n",
    "+22222\n",
    " WW\n",
    " XX\n",
    " YY\n",
    ]

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  expected_output = [
    'U         %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk @@ -13,6 +13,7 @@ with offset 18\n',
    '>         applied hunk @@ -20,6 +20,7 @@ with offset 18\n'
    ]
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/mu', contents=
                     'AA\n' 'BB\n' 'CC\n' 'DD\n' 'EE\n' 'FF\n'
                     'TT\n' 'UU\n' 'VV\n' 'WW\n' 'XX\n' 'YY\n'
                     'GG\n' 'HH\n' 'II\n' 'JJ\n' 'KK\n' 'LL\n'
                     '33333\n' '33333\n' '33333\n'
                     '33333\n' '33333\n' '33333\n'
                     '33333\n' '33333\n' '33333\n'
                     '33333\n' '33333\n' '33333\n'
                     'MM\n' 'NN\n' 'OO\n' '11111\n' 'PP\n' 'QQ\n' 'RR\n'
                     'SS\n' 'TT\n' 'UU\n' 'VV\n' '22222\n' 'WW\n' 'XX\n'
                     'YY\n' 'ZZ\n')
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='M ', wc_rev=2)
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

  sbox.simple_revert('A/mu')

  # change patch so second hunk matches at both -14 and +17, we still
  # want the second match
  unidiff_patch = [
    "Index: A/mu\n"
    "===================================================================\n",
    "--- A/mu\t(revision 1)\n",
    "+++ A/mu\t(working copy)\n",
    "@@ -13,6 +13,7 @@\n",
    " MM\n",
    " NN\n",
    " OO\n",
    "+11111\n",
    " PP\n",
    " QQ\n",
    " RR\n",
    "@@ -21,6 +21,7 @@\n",
    " TT\n",
    " UU\n",
    " VV\n",
    "+22222\n",
    " WW\n",
    " XX\n",
    " YY\n",
    ]

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  expected_output = [
    'U         %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk @@ -13,6 +13,7 @@ with offset 18\n',
    '>         applied hunk @@ -21,6 +21,7 @@ with offset 17\n'
    ]
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/mu', contents=
                     'AA\n' 'BB\n' 'CC\n' 'DD\n' 'EE\n' 'FF\n'
                     'TT\n' 'UU\n' 'VV\n' 'WW\n' 'XX\n' 'YY\n'
                     'GG\n' 'HH\n' 'II\n' 'JJ\n' 'KK\n' 'LL\n'
                     '33333\n' '33333\n' '33333\n'
                     '33333\n' '33333\n' '33333\n'
                     '33333\n' '33333\n' '33333\n'
                     '33333\n' '33333\n' '33333\n'
                     'MM\n' 'NN\n' 'OO\n' '11111\n' 'PP\n' 'QQ\n' 'RR\n'
                     'SS\n' 'TT\n' 'UU\n' 'VV\n' '22222\n' 'WW\n' 'XX\n'
                     'YY\n' 'ZZ\n')
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='M ', wc_rev=2)
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

  sbox.simple_revert('A/mu')

@Issue(4533)
def patch_hunk_avoid_reorder2(sbox):
  """avoid reordering hunks 2"""

  sbox.build()
  wc_dir = sbox.wc_dir

  sbox.simple_append('A/mu',
                     'AA\n' 'BB\n' 'CC\n' 'DD\n' 'EE\n' 'FF\n'
                     'TT\n' 'UU\n' 'VV\n' 'WW\n' 'XX\n' 'YY\n'
                     'GG\n' 'HH\n' 'II\n' 'JJ\n' 'KK\n' 'LL\n'
                     '33333\n' '33333\n' '33333\n'
                     '33333\n' '33333\n' '33333\n'
                     '33333\n' '33333\n' '33333\n'
                     '33333\n' '33333\n' '33333\n'
                     'MM\n' 'NN\n' 'OO\n' 'PP\n' 'QQ\n' 'RR\n'
                     'SS\n' 'TT\n' 'UU\n' 'VV\n' 'WW\n' 'XX\n'
                     'YY\n' 'ZZ\n', truncate=True)
  sbox.simple_commit()

  # two hunks, first matches at offset +18, second matches at both -13
  # change patch so second hunk matches at both -12 and +19, we still
  # want the second match
  unidiff_patch = [
    "Index: A/mu\n"
    "===================================================================\n",
    "--- A/mu\t(revision 1)\n",
    "+++ A/mu\t(working copy)\n",
    "@@ -13,6 +13,7 @@\n",
    " MM\n",
    " NN\n",
    " OO\n",
    "+11111\n",
    " PP\n",
    " QQ\n",
    " RR\n",
    "@@ -19,6 +19,7 @@\n",
    " TT\n",
    " UU\n",
    " VV\n",
    "+22222\n",
    " WW\n",
    " XX\n",
    " YY\n",
    ]

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  expected_output = [
    'U         %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk @@ -13,6 +13,7 @@ with offset 18\n',
    '>         applied hunk @@ -19,6 +19,7 @@ with offset 19\n'
    ]
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/mu', contents=
                     'AA\n' 'BB\n' 'CC\n' 'DD\n' 'EE\n' 'FF\n'
                     'TT\n' 'UU\n' 'VV\n' 'WW\n' 'XX\n' 'YY\n'
                     'GG\n' 'HH\n' 'II\n' 'JJ\n' 'KK\n' 'LL\n'
                     '33333\n' '33333\n' '33333\n'
                     '33333\n' '33333\n' '33333\n'
                     '33333\n' '33333\n' '33333\n'
                     '33333\n' '33333\n' '33333\n'
                     'MM\n' 'NN\n' 'OO\n' '11111\n' 'PP\n' 'QQ\n' 'RR\n'
                     'SS\n' 'TT\n' 'UU\n' 'VV\n' '22222\n' 'WW\n' 'XX\n'
                     'YY\n' 'ZZ\n')
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='M ', wc_rev=2)
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

@Issue(4533)
def patch_hunk_reorder(sbox):
  """hunks that reorder"""

  sbox.build()
  wc_dir = sbox.wc_dir

  sbox.simple_append('A/mu',
                     'AA\n' 'BB\n' 'CC\n' 'DD\n' 'EE\n' 'FF\n' 'GG\n'
                     'HH\n' 'II\n' 'JJ\n' 'KK\n' 'LL\n' 'MM\n' 'NN\n',
                     truncate=True)
  sbox.simple_commit()

  # Two hunks match in opposite order
  unidiff_patch = [
    "Index: A/mu\n"
    "===================================================================\n",
    "--- A/mu\t(revision 1)\n",
    "+++ A/mu\t(working copy)\n",
    "@@ -2,6 +2,7 @@\n",
    " II\n",
    " JJ\n",
    " KK\n",
    "+11111\n",
    " LL\n",
    " MM\n",
    " NN\n",
    "@@ -9,6 +10,7 @@\n",
    " BB\n",
    " CC\n",
    " DD\n",
    "+22222\n",
    " EE\n",
    " FF\n",
    " GG\n",
    ]

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  expected_output = [
    'U         %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk @@ -9,6 +10,7 @@ with offset -7\n',
    '>         applied hunk @@ -2,6 +2,7 @@ with offset 7\n',
    ]
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/mu', contents=
                     'AA\n' 'BB\n' 'CC\n' 'DD\n' '22222\n' 'EE\n' 'FF\n' 'GG\n'
                     'HH\n' 'II\n' 'JJ\n' 'KK\n' '11111\n' 'LL\n' 'MM\n' 'NN\n')
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='M ', wc_rev=2)
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

  # In the following case the reordered hunk2 is smaller offset
  # magnitude than hunk2 at the end and the reorder is preferred.
  sbox.simple_revert('A/mu')
  sbox.simple_append('A/mu',
                     'x\n' * 2  +
                     '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n' +
                     'x\n' * 2  +
                     '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n' +
                     'x\n' * 10  +
                     '1\n' '2\n' '3\n' 'hunk1\n' '4\n' '5\n' '6\n' +
                     'x\n' * 100  +
                     '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n',
                     truncate=True)
  sbox.simple_commit()

  unidiff_patch = [
    "Index: A/mu\n"
    "===================================================================\n",
    "--- A/mu\t(revision 2)\n",
    "+++ A/mu\t(working copy)\n",
    "@@ -28,7 +28,7 @@\n",
    " 1\n",
    " 2\n",
    " 3\n",
    "-hunk1\n",
    "+hunk1-mod\n",
    " 4\n",
    " 5\n",
    " 6\n",
    "@@ -44,7 +44,7 @@\n",
    " 1\n",
    " 2\n",
    " 3\n",
    "-hunk2\n",
    "+hunk2-mod\n",
    " 4\n",
    " 5\n",
    " 6\n",
    ]

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  expected_output = [
    'U         %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk @@ -44,7 +44,7 @@ with offset -32\n',
    '>         applied hunk @@ -28,7 +28,7 @@ with offset 1\n',
    ]
  expected_disk.tweak('A/mu', contents=
                      'x\n' * 2  +
                      '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n' +
                      'x\n' * 2  +
                      '1\n' '2\n' '3\n' 'hunk2-mod\n' '4\n' '5\n' '6\n' +
                      'x\n' * 10  +
                      '1\n' '2\n' '3\n' 'hunk1-mod\n' '4\n' '5\n' '6\n' +
                      'x\n' * 100  +
                      '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n')

  expected_status.tweak('A/mu', status='M ', wc_rev=3)
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)
  sbox.simple_revert('A/mu')

  # In this case the reordered hunk2 is further than hunk2 at the end
  # and the reordered is not preferred.
  unidiff_patch = [
    "Index: A/mu\n"
    "===================================================================\n",
    "--- A/mu\t(revision 2)\n",
    "+++ A/mu\t(working copy)\n",
    "@@ -28,7 +28,7 @@\n",
    " 1\n",
    " 2\n",
    " 3\n",
    "-hunk1\n",
    "+hunk1-mod\n",
    " 4\n",
    " 5\n",
    " 6\n",
    "@@ -110,7 +110,7 @@\n",
    " 1\n",
    " 2\n",
    " 3\n",
    "-hunk2\n",
    "+hunk2-mod\n",
    " 4\n",
    " 5\n",
    " 6\n",
    ]

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  expected_output = [
    'U         %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk @@ -28,7 +28,7 @@ with offset 1\n',
    '>         applied hunk @@ -110,7 +110,7 @@ with offset 26\n',
    ]
  expected_disk.tweak('A/mu', contents=
                      'x\n' * 2  +
                      '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n' +
                      'x\n' * 2  +
                      '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n' +
                      'x\n' * 10  +
                      '1\n' '2\n' '3\n' 'hunk1-mod\n' '4\n' '5\n' '6\n' +
                      'x\n' * 100  +
                      '1\n' '2\n' '3\n' 'hunk2-mod\n' '4\n' '5\n' '6\n')

  expected_status.tweak('A/mu', status='M ', wc_rev=3)
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

@XFail()
def patch_hunk_overlap(sbox):
  """hunks that overlap"""

  sbox.build()
  wc_dir = sbox.wc_dir

  sbox.simple_append('A/mu',
                     'AA\n' 'BB\n' 'CC\n' 'DD\n' 'EE\n' 'FF\n'
                     'GG\n' 'HH\n' 'II\n', truncate=True)
  sbox.simple_commit()

  # Two hunks that overlap when applied, GNU patch can apply both hunks.
  unidiff_patch = [
    "Index: A/mu\n"
    "===================================================================\n",
    "--- A/mu\t(revision 1)\n",
    "+++ A/mu\t(working copy)\n",
    "@@ -2,6 +2,7 @@\n",
    " BB\n",
    " CC\n",
    " DD\n",
    "+11111\n",
    " EE\n",
    " FF\n",
    " GG\n",
    "@@ -9,6 +10,7 @@\n",
    " DD\n",
    " EE\n",
    " FF\n",
    "+22222\n",
    " GG\n",
    " HH\n",
    " II\n",
    ]

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  expected_output = [
    'U         %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk @@ -9,6 +10,7 @@ with offset -5\n',
    ]
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/mu', contents=
                     'AA\n' 'BB\n' 'CC\n' 'DD\n' '11111\n' 'EE\n' 'FF\n'
                     '22222\n' 'GG\n' 'HH\n' 'II\n')
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='M ', wc_rev=2)
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

def patch_delete_modified(sbox):
  """patch delete modified"""

  sbox.build()
  wc_dir = sbox.wc_dir

  # A patch that deletes beta.
  unidiff_patch = [
    "Index: A/B/E/beta\n",
    "===================================================================\n",
    "--- A/B/E/beta	(revision 1)\n",
    "+++ A/B/E/beta	(working copy)\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'beta'.\n",
    ]

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  # First application deletes beta
  expected_output = [
    'D         %s\n' % sbox.ospath('A/B/E/beta'),
    ]
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/B/E/beta')
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/B/E/beta', status='D ')
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

  # Second application notifies already applied
  expected_output = [
    'G         %s\n' % sbox.ospath('A/B/E/beta'),
    '>         hunk @@ -1,1 +0,0 @@ already applied\n',
  ]
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

  # Third application, with file present even though state is 'D', also skips
  sbox.simple_append('A/B/E/beta', 'Modified', truncate=True)
  expected_disk.add({'A/B/E/beta' : Item(contents='Modified')})
  expected_output = [
    'Skipped \'%s\'\n' % sbox.ospath('A/B/E/beta'),
  ] + svntest.main.summary_of_conflicts(skipped_paths=1)
  expected_skip = wc.State('', {
    sbox.ospath('A/B/E/beta') :  Item(verb='Skipped'),
  })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

  # Revert and modify beta, fourth application gives a text conflict.
  sbox.simple_revert('A/B/E/beta')
  sbox.simple_append('A/B/E/beta', 'Modified', truncate=True)

  expected_output = [
    'C         %s\n' % sbox.ospath('A/B/E/beta'),
    '>         rejected hunk @@ -1,1 +0,0 @@\n',
  ] + svntest.main.summary_of_conflicts(text_conflicts=1)
  expected_skip = wc.State('', { })
  reject_file_contents = [
    "--- A/B/E/beta\n",
    "+++ /dev/null\n",
    "@@ -1,1 +0,0 @@\n",
    "-This is the file 'beta'.\n",
  ]
  expected_disk.add({'A/B/E/beta.svnpatch.rej'
                     : Item(contents=''.join(reject_file_contents))
                     })
  expected_status.tweak('A/B/E/beta', status='M ')
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

def patch_closest(sbox):
  "find closest hunk"

  sbox.build()
  wc_dir = sbox.wc_dir

  unidiff_patch = [
    "Index: A/mu\n"
    "===================================================================\n",
    "--- A/mu\t(revision 2)\n",
    "+++ A/mu\t(working copy)\n",
    "@@ -47,7 +47,7 @@\n",
    " 1\n",
    " 2\n",
    " 3\n",
    "-hunk1\n",
    "+hunk1-mod\n",
    " 4\n",
    " 5\n",
    " 6\n",
    "@@ -66,7 +66,7 @@\n",
    " 1\n",
    " 2\n",
    " 3\n",
    "-rejected-hunk2-\n",
    "+rejected-hunk2-mod\n",
    " 4\n",
    " 5\n",
    " 6\n",
    "@@ -180,7 +180,7 @@\n",
    " 1\n",
    " 2\n",
    " 3\n",
    "-hunk3\n",
    "+hunk3-mod\n",
    " 4\n",
    " 5\n",
    " 6\n",
    ]
  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch))

  # Previous offset for hunk3 is +4, hunk3 matches at relative offsets
  # of -19 and +18, prefer +18 gives final offset +22
  sbox.simple_append('A/mu',
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk1\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk3\n' '4\n' '5\n' '6\n' +
                     'x\n' * 30  +
                     '1\n' '2\n' '3\n' 'hunk3\n' '4\n' '5\n' '6\n' +
                     'x\n' * 10,
                     truncate=True)
  sbox.simple_commit()

  expected_output = [
    'C         %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk @@ -47,7 +47,7 @@ with offset 4\n',
    '>         applied hunk @@ -180,7 +180,7 @@ with offset 22\n',
    '>         rejected hunk @@ -66,7 +66,7 @@\n',
  ] + svntest.main.summary_of_conflicts(text_conflicts=1)
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({'A/mu.svnpatch.rej' : Item(contents=
    "--- A/mu\n" +
    "+++ A/mu\n" +
    "@@ -66,7 +66,7 @@\n" +
    " 1\n" +
    " 2\n" +
    " 3\n" +
    "-rejected-hunk2-\n" +
    "+rejected-hunk2-mod\n" +
    " 4\n" +
    " 5\n" +
    " 6\n")})
  expected_disk.tweak('A/mu', contents=
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk1-mod\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk3\n' '4\n' '5\n' '6\n' +
                     'x\n' * 30  +
                     '1\n' '2\n' '3\n' 'hunk3-mod\n' '4\n' '5\n' '6\n' +
                     'x\n' * 10)
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='M ', wc_rev=2)
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

  # Previous offset for hunk3 is +4, hunk3 matches at relative offsets
  # of -19 and +20, prefer -19 gives final offset -15
  sbox.simple_append('A/mu',
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk1\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk3\n' '4\n' '5\n' '6\n' +
                     'x\n' * 32  +
                     '1\n' '2\n' '3\n' 'hunk3\n' '4\n' '5\n' '6\n' +
                     'x\n' * 10,
                     truncate=True)
  sbox.simple_commit()

  os.remove(sbox.ospath('A/mu.svnpatch.rej'))
  expected_output = [
    'C         %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk @@ -47,7 +47,7 @@ with offset 4\n',
    '>         applied hunk @@ -180,7 +180,7 @@ with offset -15\n',
    '>         rejected hunk @@ -66,7 +66,7 @@\n',
  ] + svntest.main.summary_of_conflicts(text_conflicts=1)
  expected_disk.tweak('A/mu', contents=
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk1-mod\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk3-mod\n' '4\n' '5\n' '6\n' +
                     'x\n' * 32  +
                     '1\n' '2\n' '3\n' 'hunk3\n' '4\n' '5\n' '6\n' +
                     'x\n' * 10)
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='M ', wc_rev=3)
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

  # Previous offset for hunk3 is +4, hunk3 matches at relative offsets
  # of -19 and +19, prefer -19 gives final offset -15
  sbox.simple_append('A/mu',
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk1\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk3\n' '4\n' '5\n' '6\n' +
                     'x\n' * 31  +
                     '1\n' '2\n' '3\n' 'hunk3\n' '4\n' '5\n' '6\n' +
                     'x\n' * 10,
                     truncate=True)
  sbox.simple_commit()

  os.remove(sbox.ospath('A/mu.svnpatch.rej'))
  expected_output = [
    'C         %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk @@ -47,7 +47,7 @@ with offset 4\n',
    '>         applied hunk @@ -180,7 +180,7 @@ with offset -15\n',
    '>         rejected hunk @@ -66,7 +66,7 @@\n',
  ] + svntest.main.summary_of_conflicts(text_conflicts=1)
  expected_disk.tweak('A/mu', contents=
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk1-mod\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk3-mod\n' '4\n' '5\n' '6\n' +
                     'x\n' * 31  +
                     '1\n' '2\n' '3\n' 'hunk3\n' '4\n' '5\n' '6\n' +
                     'x\n' * 10)
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='M ', wc_rev=4)
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

  # Previous offset for hunk3 is +4, hunk3 matches at relative offsets
  # of +173 and -173, prefer +173 gives final offset +177
  sbox.simple_append('A/mu',
                     'x\n' * 10  +
                     '1\n' '2\n' '3\n' 'hunk3\n' '4\n' '5\n' '6\n' +
                     'x\n' * 33 +
                     '1\n' '2\n' '3\n' 'hunk1\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n' +
                     'x\n' * 242  +
                     '1\n' '2\n' '3\n' 'hunk3\n' '4\n' '5\n' '6\n' +
                     'x\n' * 10,
                     truncate=True)
  sbox.simple_commit()

  os.remove(sbox.ospath('A/mu.svnpatch.rej'))
  expected_output = [
    'C         %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk @@ -47,7 +47,7 @@ with offset 4\n',
    '>         applied hunk @@ -180,7 +180,7 @@ with offset 177\n',
    '>         rejected hunk @@ -66,7 +66,7 @@\n',
  ] + svntest.main.summary_of_conflicts(text_conflicts=1)
  expected_disk.tweak('A/mu', contents=
                     'x\n' * 10  +
                     '1\n' '2\n' '3\n' 'hunk3\n' '4\n' '5\n' '6\n' +
                     'x\n' * 33  +
                     '1\n' '2\n' '3\n' 'hunk1-mod\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n' +
                     'x\n' * 242  +
                     '1\n' '2\n' '3\n' 'hunk3-mod\n' '4\n' '5\n' '6\n' +
                     'x\n' * 10)
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='M ', wc_rev=5)
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

  # Previous offset for hunk3 is +4, hunk3 matches at relative offsets
  # of +174 and -173, prefer -173 gives final offset -169
  sbox.simple_append('A/mu',
                     'x\n' * 10  +
                     '1\n' '2\n' '3\n' 'hunk3\n' '4\n' '5\n' '6\n' +
                     'x\n' * 33 +
                     '1\n' '2\n' '3\n' 'hunk1\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n' +
                     'x\n' * 243  +
                     '1\n' '2\n' '3\n' 'hunk3\n' '4\n' '5\n' '6\n' +
                     'x\n' * 10,
                     truncate=True)
  sbox.simple_commit()

  os.remove(sbox.ospath('A/mu.svnpatch.rej'))
  expected_output = [
    'C         %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk @@ -180,7 +180,7 @@ with offset -169\n',
    '>         applied hunk @@ -47,7 +47,7 @@ with offset 4\n',
    '>         rejected hunk @@ -66,7 +66,7 @@\n',
  ] + svntest.main.summary_of_conflicts(text_conflicts=1)
  expected_disk.tweak('A/mu', contents=
                     'x\n' * 10  +
                     '1\n' '2\n' '3\n' 'hunk3-mod\n' '4\n' '5\n' '6\n' +
                     'x\n' * 33  +
                     '1\n' '2\n' '3\n' 'hunk1-mod\n' '4\n' '5\n' '6\n' +
                     'x\n' * 50  +
                     '1\n' '2\n' '3\n' 'hunk2\n' '4\n' '5\n' '6\n' +
                     'x\n' * 243  +
                     '1\n' '2\n' '3\n' 'hunk3\n' '4\n' '5\n' '6\n' +
                     'x\n' * 10)
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='M ', wc_rev=6)
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

@SkipUnless(svntest.main.is_posix_os)
def patch_symlink_traversal(sbox):
  """symlink traversal behaviour"""

  sbox.build(read_only=True)
  wc_dir = sbox.wc_dir
  alpha_contents = "This is the file 'alpha'.\n"

  # A/B/E/unversioned -> alpha
  # A/B/E/versioned -> alpha
  # A/B/unversioned -> E         (so A/B/unversioned/alpha is A/B/E/alpha)
  # A/B/versioned -> E           (so A/B/versioned/alpha is A/B/E/alpha)
  os.symlink('alpha', sbox.ospath('A/B/E/unversioned'))
  os.symlink('alpha', sbox.ospath('A/B/E/versioned'))
  os.symlink('E', sbox.ospath('A/B/unversioned'))
  os.symlink('E', sbox.ospath('A/B/versioned'))
  sbox.simple_add('A/B/E/versioned', 'A/B/versioned')

  prepatch_status = svntest.actions.get_virginal_state(wc_dir, 1)
  prepatch_status.add({'A/B/E/versioned' : Item(status='A ', wc_rev='-')})
  prepatch_status.add({'A/B/versioned' : Item(status='A ', wc_rev='-')})
  svntest.actions.run_and_verify_status(wc_dir, prepatch_status)

  # Patch through unversioned symlink to file
  unidiff_patch = (
    "Index: A/B/E/unversioned\n"
    "===================================================================\n"
    "--- A/B/E/unversioned\t(revision 2)\n"
    "+++ A/B/E/unversioned\t(working copy)\n"
    "@@ -1 +1,2 @@\n"
    " This is the file 'alpha'.\n"
    "+xx\n"
    )
  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, unidiff_patch)

  expected_output = wc.State(wc_dir, {
  })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({'A/B/E/unversioned' : Item(contents=alpha_contents)})
  expected_disk.add({'A/B/E/versioned' : Item(contents=alpha_contents)})
  expected_disk.add({'A/B/unversioned' : Item()})
  expected_disk.add({'A/B/versioned' : Item()})
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({'A/B/E/versioned' : Item(status='A ', wc_rev='-')})
  expected_status.add({'A/B/versioned' : Item(status='A ', wc_rev='-')})
  expected_skip = wc.State(wc_dir, {
    'A/B/E/unversioned' : Item(verb='Skipped'),
  })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)
  svntest.actions.run_and_verify_status(wc_dir, prepatch_status)

  # Patch through versioned symlink to file
  unidiff_patch = (
    "Index: A/B/E/versioned\n"
    "===================================================================\n"
    "--- A/B/E/versioned\t(revision 2)\n"
    "+++ A/B/E/versioned\t(working copy)\n"
    "@@ -1 +1,2 @@\n"
    " This is the file 'alpha'.\n"
    "+xx\n"
    )
  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, unidiff_patch)
  reject_contents = (
    "--- A/B/E/versioned\n"
    "+++ A/B/E/versioned\n"
    "@@ -1,1 +1,2 @@\n"
    " This is the file 'alpha'.\n"
    "+xx\n"
  )

  expected_output = wc.State(wc_dir, {
    'A/B/E/versioned' : Item(status='C ')
  })
  expected_disk.add({
     'A/B/E/versioned.svnpatch.rej' : Item(contents=reject_contents)
  })
  expected_skip = wc.State(wc_dir, { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)
  os.remove(sbox.ospath('A/B/E/versioned.svnpatch.rej'))
  expected_disk.remove('A/B/E/versioned.svnpatch.rej')
  svntest.actions.run_and_verify_status(wc_dir, prepatch_status)

  # Patch through unversioned symlink to parent of file
  unidiff_patch = (
    "Index: A/B/unversioned/alpha\n"
    "===================================================================\n"
    "--- A/B/unversioned/alpha\t(revision 2)\n"
    "+++ A/B/unversioned/alpha\t(working copy)\n"
    "@@ -1 +1,2 @@\n"
    " This is the file 'alpha'.\n"
    "+xx\n"
    )
  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, unidiff_patch)

  expected_output = wc.State(wc_dir, {})
  expected_skip = wc.State(wc_dir, {
    'A/B/unversioned/alpha' : Item(verb='Skipped'),
  })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)
  svntest.actions.run_and_verify_status(wc_dir, prepatch_status)

  # Patch through versioned symlink to parent of file
  unidiff_patch = (
    "Index: A/B/versioned/alpha\n"
    "===================================================================\n"
    "--- A/B/versioned/alpha\t(revision 2)\n"
    "+++ A/B/versioned/alpha\t(working copy)\n"
    "@@ -1 +1,2 @@\n"
    " This is the file 'alpha'.\n"
    "+xx\n"
    )
  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, unidiff_patch)

  expected_output = wc.State(wc_dir, {})
  expected_skip = wc.State(wc_dir, {
    'A/B/versioned/alpha' :  Item(verb='Skipped'),
  })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)
  svntest.actions.run_and_verify_status(wc_dir, prepatch_status)

@SkipUnless(svntest.main.is_posix_os)
def patch_obstructing_symlink_traversal(sbox):
  """obstructing symlink traversal behaviour"""

  sbox.build()
  wc_dir = sbox.wc_dir
  alpha_contents = "This is the file 'alpha'.\n"
  sbox.simple_append('A/B/F/alpha', alpha_contents)
  sbox.simple_add('A/B/F/alpha')
  sbox.simple_commit()
  sbox.simple_update()

  # Unversioned symlink A/B/E -> F obstructing versioned A/B/E so
  # versioned A/B/E/alpha is A/B/F/alpha
  svntest.main.safe_rmtree(sbox.ospath('A/B/E'))
  os.symlink('F', sbox.ospath('A/B/E'))

  unidiff_patch = (
    "Index: A/B/E/alpha\n"
    "===================================================================\n"
    "--- A/B/E/alpha\t(revision 2)\n"
    "+++ A/B/E/alpha\t(working copy)\n"
    "@@ -1 +1,2 @@\n"
    " This is the file 'alpha'.\n"
    "+xx\n"
    )
  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, unidiff_patch)

  ### Patch applies through the unversioned symlink
  expected_output = [
    'U         %s\n' % sbox.ospath('A/B/E/alpha'),
  ]
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/B/E/alpha', 'A/B/E/beta')
  expected_disk.add({'A/B/F/alpha' : Item(contents=alpha_contents+"xx\n")})
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.add({'A/B/F/alpha' : Item(status='  ', wc_rev=2)})
  expected_status.tweak('A/B/E', status='~ ')
  expected_status.tweak('A/B/E/alpha', 'A/B/F/alpha', status='M ')
  expected_status.tweak('A/B/E/beta', status='! ')
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

def patch_binary_file(sbox):
  "patch a binary file"

  sbox.build()
  wc_dir = sbox.wc_dir

  # Make the file binary by putting some non ascii chars inside or propset
  # will return a warning
  sbox.simple_append('iota', b'\0\202\203\204\205\206\207nsomething\nelse\xFF')
  sbox.simple_propset('svn:mime-type', 'application/binary', 'iota')

  expected_output = [
    'Index: svn-test-work/working_copies/patch_tests-57/iota\n',
    '===================================================================\n',
    'diff --git a/iota b/iota\n',
    'GIT binary patch\n',
    'literal 48\n',
    'zc$^E#$ShU>qLPeMg|y6^R0Z|S{E|d<JuZf(=9bpB_PpZ!+|-hc%)E52)STkf{{Wp*\n',
    'B5)uFa\n',
    '\n',
    'literal 25\n',
    'ec$^E#$ShU>qLPeMg|y6^R0Z|S{E|d<JuU!m{s;*G\n',
    '\n',
    'Property changes on: iota\n',
    '___________________________________________________________________\n',
    'Added: svn:mime-type\n',
    '## -0,0 +1 ##\n',
    '+application/binary\n',
    '\ No newline at end of property\n',
  ]

  _, diff_output, _ = svntest.actions.run_and_verify_svn(expected_output, [],
                                                         'diff', '--git',
                                                         wc_dir)

  sbox.simple_revert('iota')

  tmp = sbox.get_tempname()
  svntest.main.file_write(tmp, ''.join(diff_output))

  expected_output = wc.State(wc_dir, {
    'iota'              : Item(status='UU'),
  })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('iota',
                      props={'svn:mime-type':'application/binary'},
                      contents =
                      b'This is the file \'iota\'.\n' +
                      b'\0\202\203\204\205\206\207nsomething\nelse\xFF')
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', status='MM')
  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, tmp,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # Ok, now try applying it backwards
  expected_output.tweak('iota', status='UU')
  expected_disk = svntest.main.greek_state.copy()
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  svntest.actions.run_and_verify_patch(wc_dir, tmp,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], False, True, '--reverse-diff')

def patch_delete_nodes(sbox):
  "apply deletes via patch"

  sbox.build()
  wc_dir = sbox.wc_dir

  sbox.simple_propset('A', 'B', 'A/B/E/alpha')
  sbox.simple_append('A/mu', '\0')
  sbox.simple_propset('svn:mime-type', 'application/nonsense', 'A/mu')

  sbox.simple_commit() # r2
  sbox.simple_update()

  expected_skip = wc.State('', { })

  original_status = svntest.actions.get_virginal_state(wc_dir, 2)
  original_disk = svntest.main.greek_state.copy()
  original_disk.tweak('A/mu',
                      props={'svn:mime-type':'application/nonsense'},
                      contents = 'This is the file \'mu\'.\n\0')
  original_disk.tweak('A/B/E/alpha', props={'A':'B'})
  svntest.actions.run_and_verify_status(wc_dir, original_status)
  svntest.actions.verify_disk(wc_dir, original_disk, True)

  sbox.simple_rm('A/B/E/alpha', 'A/B/E/beta', 'A/mu')

  _, diff, _ = svntest.actions.run_and_verify_svn(None, [],
                                                  'diff', '--git', wc_dir)

  patch = sbox.get_tempname('patch')
  svntest.main.file_write(patch, ''.join(diff))

  deleted_status = original_status.copy()
  deleted_disk = original_disk.copy()
  deleted_disk.remove('A/B/E/alpha', 'A/B/E/beta', 'A/mu')
  deleted_status.tweak('A/B/E/alpha', 'A/B/E/beta', 'A/mu', status='D ')


  svntest.actions.run_and_verify_status(wc_dir, deleted_status)
  svntest.actions.verify_disk(wc_dir, deleted_disk, True)

  # And now apply the patch from the clean state
  sbox.simple_revert('A/B/E/alpha', 'A/B/E/beta', 'A/mu')

  # Expect that the hint 'empty dir? -> delete dir' deletes 'E'
  # ### A smarter diff format might change this in a future version
  deleted_disk.remove('A/B/E')
  deleted_status.tweak('A/B/E', status='D ')
  expected_output = wc.State(wc_dir, {
    'A/mu'              : Item(status='D '),
    'A/B/E'             : Item(status='D '),
    'A/B/E/beta'        : Item(status='D '),
    'A/B/E/alpha'       : Item(status='D '),
  })

  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, deleted_disk,
                                       deleted_status, expected_skip,
                                       [], False, True)

  # And let's see if we can apply the reverse version of the patch
  expected_output = wc.State(wc_dir, {
    'A/mu'              : Item(status='A '),
    'A/B/E'             : Item(status='A '),
    'A/B/E/beta'        : Item(status='A '),
    'A/B/E/alpha'       : Item(status='A '),
  })
  original_status.tweak('A/mu', status='RM') # New file
  original_status.tweak('A/B/E', status='R ') # New dir
  original_status.tweak('A/B/E/alpha', 'A/B/E/beta',
                        status='A ', wc_rev='-',
                        entry_status='R ', entry_rev='2')


  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, original_disk,
                                       original_status, expected_skip,
                                       [], True, True, '--reverse-diff')

def patch_delete_missing_eol(sbox):
  "apply a delete missing an eol"

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

  delete_patch = [
    "Index: A/B/E/beta\n",
    "===================================================================\n",
    "--- A/B/E/beta	(revision 1)\n",
    "+++ /dev/null\n",
    "@@ -1 +0,0 @@\n",
    "-This is the file 'beta'." # No final EOL
  ]

  patch = sbox.get_tempname('patch')
  svntest.main.file_write(patch, ''.join(delete_patch))

  expected_output = wc.State(wc_dir, {
    'A/B/E/beta'        : Item(status='D '),
  })
  expected_skip = wc.State(wc_dir, {
  })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/B/E/beta', status='D ')
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.remove('A/B/E/beta')

  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], False, True)

  # Try again? -> Merged
  expected_output = wc.State(wc_dir, {
    'A/B/E/beta'        : Item(status='G '),
  })
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], False, True)

  # Reverse
  expected_output = wc.State(wc_dir, {
    'A/B/E/beta'        : Item(status='A '),
  })
  expected_skip = wc.State(wc_dir, {
  })
  expected_disk = svntest.main.greek_state.copy()
  expected_status.tweak('A/B/E/beta', status='R ')
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], False, True, '--reverse-diff')

  # Try again? -> Already applied
  expected_output = wc.State(wc_dir, {
    'A/B/E/beta'        : Item(status='G '),
  })
  expected_skip = wc.State(wc_dir, {
  })
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], False, True, '--reverse-diff')

def patch_final_eol(sbox):
  "patch the final eol"

  sbox.build()
  wc_dir = sbox.wc_dir

  delete_patch = [
   'Index: A/mu\n',
   '===================================================================\n',
   '--- A/mu\t(revision 1)\n',
   '+++ A/mu\t(working copy)\n',
   '@@ -1 +1 @@\n',
   '-This is the file \'mu\'.\n',
   '+This is the file \'mu\'.\n',
   '\ No newline at end of file\n',
   'Index: iota\n',
   '===================================================================\n',
   '--- iota\t(revision 1)\n',
   '+++ iota\t(working copy)\n',
   '@@ -1 +1 @@\n',
   '-This is the file \'iota\'.\n',
   '+This is the file \'iota\'.\n',
   '\ No newline at end of file' # Missing EOL
  ]

  patch = sbox.get_tempname('patch')
  # We explicitly use wb here as this is the eol type added later in the test
  svntest.main.file_write(patch, ''.join(delete_patch), mode='wb')

  expected_output = wc.State(wc_dir, {
    'A/mu'        : Item(status='U '),
    'iota'        : Item(status='U '),
  })
  expected_skip = wc.State(wc_dir, {})
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', 'A/mu', status='M ')
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('iota', contents="This is the file 'iota'.")
  expected_disk.tweak('A/mu', contents="This is the file 'mu'.")

  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], False, True)

  # And again - Still U as patch doesn't check final EOL of source
  expected_output.tweak('iota', 'A/mu', status='U ')
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], False, True)

  # Reverse
  expected_output.tweak('iota', 'A/mu', status='U ')
  expected_disk.tweak('iota', contents="This is the file 'iota'.\n")
  expected_disk.tweak('A/mu', contents="This is the file 'mu'.\n")
  expected_status.tweak('iota', 'A/mu', status='  ')
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], False, True, '--reverse-diff')

  # And once more
  expected_output.tweak('iota', 'A/mu', status='U ')
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], False, True, '--reverse-diff')

  # Change the unmodified form
  sbox.simple_append('iota', 'This is the file \'iota\'.', truncate=True)
  sbox.simple_append('A/mu', 'This is the file \'mu\'.', truncate=True)
  sbox.simple_commit()
  expected_status.tweak('iota', 'A/mu', wc_rev='2')

  add_patch = [
    'Index: A/mu\n',
    '===================================================================\n',
    '--- A/mu\t(revision 2)\n',
    '+++ A/mu\t(working copy)\n',
    '@@ -1 +1 @@\n',
    '-This is the file \'mu\'.\n',
    '\ No newline at end of file\n',
    '+This is the file \'mu\'.\n',
    'Index: iota\n',
    '===================================================================\n',
    '--- iota\t(revision 2)\n',
    '+++ iota\t(working copy)\n',
    '@@ -1 +1 @@\n',
    '-This is the file \'iota\'.\n',
    '\ No newline at end of file\n',
    '+This is the file \'iota\'.' # Missing eol
  ]

  svntest.main.file_write(patch, ''.join(add_patch), mode='wb')

  # Apply the patch
  expected_output.tweak('iota', 'A/mu', status='U ')
  expected_disk.tweak('iota', contents="This is the file 'iota'.\n")
  expected_disk.tweak('A/mu', contents="This is the file 'mu'.\n")
  expected_status.tweak('iota', 'A/mu', status='M ')
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], False, True)

  # And again
  expected_output.tweak('iota', 'A/mu', status='U ')
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], False, True)

  # And in reverse
  expected_output.tweak('iota', 'A/mu', status='U ')
  expected_disk.tweak('iota', contents="This is the file 'iota'.")
  expected_disk.tweak('A/mu', contents="This is the file 'mu'.")
  expected_status.tweak('iota', 'A/mu', status='  ')
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], False, True, '--reverse-diff')

  # And again
  expected_output.tweak('iota', 'A/mu', status='U ')
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], False, True, '--reverse-diff')

def patch_adds_executability_nocontents(sbox):
  """patch adds svn:executable, without contents"""

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

  unidiff_patch = (
    "diff --git a/iota b/iota\n"
    "old mode 100644\n"
    "new mode 100755\n"
    )
  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, unidiff_patch)

  expected_output = wc.State(wc_dir, {
    'iota' : Item(status=' U')
  })
  expected_disk = svntest.main.greek_state.copy()
  # "*" is SVN_PROP_EXECUTABLE_VALUE aka SVN_PROP_BOOLEAN_TRUE
  expected_disk.tweak('iota', props={'svn:executable': '*'})

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', status=' M')

  expected_skip = wc.State(wc_dir, { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       check_props=True)

  # And try it again
  # This may produce different output but must have the same result
  expected_output.tweak('iota', status=' G')
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       check_props=True)

  # And then try it in reverse
  expected_disk.tweak('iota', props={})
  expected_status.tweak('iota', status='  ')
  expected_output.tweak('iota', status=' U')
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True, '--reverse-diff')

  # And try it again
  # This may produce different output but must have the same result
  expected_output.tweak('iota', status=' G')
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True, '--reverse-diff')

def patch_adds_executability_nocontents2(sbox):
  "patch adds svn:executable, without contents 2"

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

  unidiff_patch = (
    "diff --git a/new b/new\n"
    "old mode 100644\n"
    "new mode 100755\n"
    )
  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, unidiff_patch)

  expected_output = wc.State(wc_dir, {
  })
  expected_disk = svntest.main.greek_state.copy()
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)

  expected_skip = wc.State(wc_dir, {
    'new' : Item(verb='Skipped missing target')
  })

  # This creates 'new', while a skip or reject is expected
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)


def patch_adds_executability_yescontents(sbox):
  """patch adds svn:executable, with contents"""

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

  mu_new_contents = (
    "This is the file 'mu'.\n"
    "with text mods too\n"
    )

  unidiff_patch = (
    "diff --git a/A/mu b/A/mu\n"
    "old mode 100644\n"
    "new mode 100755\n"
    "index 8a0f01c..dfad3ac\n"
    "--- a/A/mu\n"
    "+++ b/A/mu\n"
    "@@ -1 +1,2 @@\n"
    " This is the file 'mu'.\n"
    "+with text mods too\n"
    )
  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, unidiff_patch)

  expected_output = [
    'UU        %s\n' % sbox.ospath('A/mu'),
  ]
  expected_disk = svntest.main.greek_state.copy()
  # "*" is SVN_PROP_EXECUTABLE_VALUE aka SVN_PROP_BOOLEAN_TRUE
  expected_disk.tweak('A/mu', props={'svn:executable': '*'})
  expected_disk.tweak('A/mu', contents=mu_new_contents)

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/mu', status='MM')

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       check_props=True)

def patch_deletes_executability(sbox):
  """patch deletes svn:executable"""

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

  ## Set up the basic state.
  sbox.simple_propset('svn:executable', 'yes', 'iota')
  #sbox.simple_commit(target='iota', message="Make 'iota' executable.")

  unidiff_patch = (
    "diff --git a/iota b/iota\n"
    "old mode 100755\n"
    "new mode 100644\n"
    )
  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, unidiff_patch)

  expected_output = [
    ' U        %s\n' % sbox.ospath('iota'),
  ]
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('iota') # props=None by default

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

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       check_props=True)

def patch_ambiguous_executability_contradiction(sbox):
  """patch ambiguous svn:executable, bad"""

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

  unidiff_patch = (
    "Index: iota\n"
    "===================================================================\n"
    "diff --git a/iota b/iota\n"
    "old mode 100755\n"
    "new mode 100644\n"
    "Property changes on: iota\n"
    "-------------------------------------------------------------------\n"
    "Added: svn:executable\n"
    "## -0,0 +1 ##\n"
    "+*\n"
    )
  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, unidiff_patch)

  expected_output = []

  expected_disk = svntest.main.greek_state.copy()

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

  expected_skip = wc.State('', { })

  error_re_string = r'.*Invalid patch:.*contradicting.*mode.*svn:executable'
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       error_re_string=error_re_string,
                                       check_props=True)

def patch_ambiguous_executability_consistent(sbox):
  """patch ambiguous svn:executable, good"""

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

  unidiff_patch = (
    "Index: iota\n"
    "===================================================================\n"
    "diff --git a/iota b/iota\n"
    "old mode 100644\n"
    "new mode 100755\n"
    "Property changes on: iota\n"
    "-------------------------------------------------------------------\n"
    "Added: svn:executable\n"
    "## -0,0 +1 ##\n"
    "+*\n"
    )
  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, unidiff_patch)

  expected_output = [
    ' U        %s\n' % sbox.ospath('iota'),
  ]

  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('iota', props={'svn:executable': '*'})

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', status=' M')

  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       error_re_string=None,
                                       check_props=True)

def patch_prop_madness(sbox):
  "patch property madness"

  sbox.build()
  wc_dir = sbox.wc_dir

  sbox.simple_propset('mod_s', 'value\n',
                      'iota', 'A/mu')

  sbox.simple_propset('mod_s_n', 'no-eol',
                      'iota', 'A/mu')

  sbox.simple_propset('mod_l', 'this\nis\na\nvery\nvery\nlong\nvalue.\n',
                      'iota', 'A/mu')

  sbox.simple_propset('mod_l_n', 'this\nis\na\nvery\nvery\nlong\nvalue.\n'
                      'without\neol', # No eol at end
                      'iota', 'A/mu')

  sbox.simple_propset('del', 'value\n',
                      'iota', 'A/mu')

  sbox.simple_propset('del_n', 'no-eol',
                      'iota', 'A/mu')

  sbox.simple_commit()

  r2_props = {
   'mod_l_n' : 'this\nis\na\nvery\nvery\nlong\nvalue.\nwithout\neol',
   'mod_l'   : 'this\nis\na\nvery\nvery\nlong\nvalue.\n',
   'mod_s'   : 'value\n',
   'mod_s_n' : 'no-eol',
   'del'     : 'value\n',
   'del_n'   : 'no-eol',
  }
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('iota', 'A/mu', props=r2_props)

  sbox.simple_propset('mod_s', 'other\n',
                      'iota', 'A/mu')

  sbox.simple_propset('mod_s_n', 'still no eol',
                      'iota', 'A/mu')

  sbox.simple_propset('mod_l', 'this\nis\na\nsomewhat\nlong\nvalue.\n',
                      'iota', 'A/mu')

  sbox.simple_propset('mod_l_n', 'this\nis\na\nanother\n..\nlong\nvalue.\n'
                      'without\neol', # No eol at end
                      'iota', 'A/mu')

  sbox.simple_propdel('del', 'iota', 'A/mu')

  sbox.simple_propdel('del_n', 'iota', 'A/mu')

  sbox.simple_propset('add_s', 'new-value\n',
                      'iota', 'A/mu')

  sbox.simple_propset('add_s_n', 'new other no eol',
                      'iota', 'A/mu')

  sbox.simple_propset('add_l', 'this\nis\nsomething\n',
                      'iota', 'A/mu')

  sbox.simple_propset('add_l_n', 'this\nhas\nno\neol', # No eol at end
                      'iota', 'A/mu')

  _, output, _ = svntest.actions.run_and_verify_svn(None, [],
                                                    'diff', wc_dir)

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

  new_props = {
    'mod_s'       : 'other\n',
    'mod_s_n'     : 'still no eol',
    'mod_l'       : 'this\nis\na\nsomewhat\nlong\nvalue.\n',
    'mod_l_n'     : 'this\nis\na\nanother\n..\nlong\nvalue.\nwithout\neol',
    'add_s'       : 'new-value\n',
    'add_s_n'     : 'new other no eol',
    'add_l'       : 'this\nis\nsomething\n',
    'add_l_n'     : 'this\nhas\nno\neol'
  }

  expected_status.tweak('iota', 'A/mu', status=' M', wc_rev='2')
  expected_disk.tweak('iota', 'A/mu', props=new_props)

  svntest.actions.verify_disk(wc_dir, expected_disk, True)
  #svntest.actions.run_and_verify_status(wc_dir, expected_status)

  svntest.actions.run_and_verify_svn(None, [],
                                     'revert', wc_dir, '-R')

  patch = sbox.get_tempname('patch')
  svntest.main.file_write(patch, ''.join(output), mode='wb')

  expected_output = wc.State(wc_dir, {
    'A/mu' : Item(status=' U'),
    'iota' : Item(status=' U'),
  })
  expected_skip= wc.State(wc_dir, {
  })

  strip_count = wc_dir.count(os.path.sep)+1

  # Patch once
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count)

  # Patch again
  expected_output.tweak('A/mu', 'iota', status=' G')
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count)

  # Reverse
  expected_output.tweak('A/mu', 'iota', status=' U')
  expected_disk.tweak('A/mu', 'iota', props=r2_props)
  expected_status.tweak('A/mu', 'iota', status='  ')
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff',
                                       '--strip', strip_count)

  # And repeat
  expected_output.tweak('A/mu', 'iota', status=' G')
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff',
                                       '--strip', strip_count)

  # Ok, and now introduce some conflicts

  sbox.simple_propset('del', 'value', 'iota') # Wrong EOL
  sbox.simple_propset('del_n', 'regeleinde\n', 'iota') # Wrong EOL+value

  sbox.simple_propset('del', 'waarde', 'A/mu') # Wrong EOL+value
  sbox.simple_propset('del_n', 'no-eol\n', 'A/mu') # Wrong EOL

  expected_output.tweak('A/mu', 'iota', status=' C')
  expected_status.tweak('iota', 'A/mu', status=' M')

  iota_props = new_props.copy()
  iota_props['del_n'] = 'regeleinde\n'
  mu_props = new_props.copy()
  mu_props['del'] = 'waarde'
  expected_disk.tweak('iota', props=iota_props)
  expected_disk.tweak('A/mu', props=mu_props)

  expected_disk.add({
   'A/mu.svnpatch.rej' : Item(contents="--- %s\n"
                                       "+++ %s\n"
                                       "Property: del\n"
                                       "## -1,1 +0,0 ##\n"
                                       "-value\n"
                                       % (sbox.path('A/mu'),
                                          sbox.path('A/mu'))),
   'iota.svnpatch.rej' : Item(contents="--- %s\n"
                                       "+++ %s\n"
                                       "Property: del_n\n"
                                       "## -1,1 +0,0 ##\n"
                                       "-no-eol\n"
                                       "\\ No newline at end of property\n"
                                       % (sbox.path('iota'),
                                          sbox.path('iota'))),
  })

  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count)

def patch_empty_vs_delete(sbox):
  "patch empty vs delete"

  sbox.build(read_only = True)
  wc_dir = sbox.wc_dir
  strip_count = wc_dir.count(os.path.sep)+1

  sbox.simple_append('iota', '', truncate=True)

  _, empty_diff, _ = svntest.actions.run_and_verify_svn(None, [],
                                                        'diff', wc_dir)

  _, empty_git, _ = svntest.actions.run_and_verify_svn(None, [],
                                                       'diff', wc_dir, '--git')

  svntest.actions.run_and_verify_svn(None, [],
                                     'rm', '--force', sbox.ospath('iota'))

  _, del_diff, _ = svntest.actions.run_and_verify_svn(None, [],
                                                      'diff', wc_dir)

  _, del_git, _ = svntest.actions.run_and_verify_svn(None, [],
                                                     'diff', wc_dir, '--git')

  empty_patch = sbox.get_tempname('empty.patch')
  svntest.main.file_write(empty_patch, ''.join(empty_diff), mode='wb')

  empty_git_patch = sbox.get_tempname('git.empty.patch')
  svntest.main.file_write(empty_git_patch, ''.join(empty_git), mode='wb')

  del_patch = sbox.get_tempname('del.patch')
  svntest.main.file_write(del_patch, ''.join(del_diff), mode='wb')

  del_git_patch = sbox.get_tempname('git.del.patch')
  svntest.main.file_write(del_git_patch, ''.join(del_git), mode='wb')

  svntest.actions.run_and_verify_svn(None, [],
                                     'revert', sbox.ospath('iota'))

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_disk = svntest.main.greek_state.copy()
  expected_skip = svntest.wc.State(wc_dir, {})


  # Git diff to empty file - Expect empty file
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(status='U ')
  })
  expected_disk.tweak('iota', contents='')
  expected_status.tweak('iota', status='M ')
  svntest.actions.run_and_verify_patch(wc_dir, empty_git_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # Retry
  expected_output.tweak('iota', status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, empty_git_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  svntest.actions.run_and_verify_svn(None, [],
                                     'revert', sbox.ospath('iota'))

  # Ordinary (unified) diff to empty file - Expect deleted
  expected_output = svntest.wc.State(wc_dir, {
    'iota' : Item(status='D ')
  })
  expected_disk.remove('iota')
  expected_status.tweak('iota', status='D ')

  svntest.actions.run_and_verify_patch(wc_dir, empty_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count)

  # Retry
  expected_output.tweak('iota', status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, empty_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count)

  svntest.actions.run_and_verify_svn(None, [],
                                     'revert', sbox.ospath('iota'))

  # Ordinary diff to deleted
  expected_output.tweak('iota', status='D ')
  svntest.actions.run_and_verify_patch(wc_dir, del_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count)

  # Retry
  expected_output.tweak('iota', status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, del_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count)

  svntest.actions.run_and_verify_svn(None, [],
                                     'revert', sbox.ospath('iota'))

  # Git diff to deleted
  expected_output.tweak('iota', status='D ')
  svntest.actions.run_and_verify_patch(wc_dir, del_git_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # Retry
  expected_output.tweak('iota', status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, del_git_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # # Not needed. Result of previous test
  #svntest.actions.run_and_verify_svn(None, [],
  #                                   'rm', '--force', sbox.ospath('iota'))

  # Ok, and now let's check what happens on reverse diffs with nothing
  # there

  # Git empty patch -> skip... target not found
  expect_no_output = svntest.wc.State(wc_dir, {})
  expect_skip_iota = svntest.wc.State(wc_dir, {
    'iota' : Item(verb='Skipped')
  })
  svntest.actions.run_and_verify_patch(wc_dir, empty_git_patch,
                                       expect_no_output, expected_disk,
                                       expected_status, expect_skip_iota,
                                       [], True, True,
                                       '--reverse-diff')

  # # Not needed. Result of previous test
  #svntest.actions.run_and_verify_svn(None, [],
  #                                   'rm', '--force', sbox.ospath('iota'))

  # Unified empty patch -> Create iota
  expected_output.tweak('iota', status='A ')
  expected_status.tweak('iota', status='R ')
  expected_disk.add({
    'iota' : Item(contents="This is the file 'iota'.\n")
  })
  svntest.actions.run_and_verify_patch(wc_dir, empty_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count,
                                       '--reverse-diff')
  # And retry
  expected_output.tweak('iota', status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, empty_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count,
                                       '--reverse-diff')

  svntest.actions.run_and_verify_svn(None, [],
                                     'rm', '--force', sbox.ospath('iota'))

  expected_output.tweak('iota', status='A ')
  svntest.actions.run_and_verify_patch(wc_dir, del_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count,
                                       '--reverse-diff')
  # And retry
  expected_output.tweak('iota', status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, del_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count,
                                       '--reverse-diff')

  svntest.actions.run_and_verify_svn(None, [],
                                     'rm', '--force', sbox.ospath('iota'))

  expected_output.tweak('iota', status='A ')
  svntest.actions.run_and_verify_patch(wc_dir, del_git_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')
  # And retry
  expected_output.tweak('iota', status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, del_git_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

def patch_add_remove_executable(sbox):
  "add and remove executable file"

  sbox.build()
  wc_dir = sbox.wc_dir

  eicar_data = 'X5O!P%@AP[4\PZX54(P^)7CC)7}$' \
               'EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*\0'
  other_data = 'X5O!P%@AP[4\PZX54(P^)7CC)7}$' \
               'SOME-LESS-INTERESTING-OTHER-TEXT!!!$H+H*\0' \
               '\0\0\0\0\0\0\0\0'

  # Write out an actual MS-DOS program
  sbox.simple_add_text(eicar_data, 'eicar.com')
  sbox.simple_propset('svn:executable', 'x', 'eicar.com')

  _, diff_add, _ = svntest.actions.run_and_verify_svn(None, [],
                                                      'diff', '--git', wc_dir)

  sbox.simple_commit()

  sbox.simple_append('eicar.com', other_data, truncate=True)
  sbox.simple_propdel('svn:executable', 'eicar.com')

  _, diff_edit, _ = svntest.actions.run_and_verify_svn(None, [],
                                                       'diff', '--git', wc_dir)

  sbox.simple_commit()
  sbox.simple_rm('eicar.com')

  _, diff_rm, _ = svntest.actions.run_and_verify_svn(None, [],
                                                     'diff', '--git', wc_dir)

  add_patch = sbox.get_tempname('add.patch')
  svntest.main.file_write(add_patch, ''.join(diff_add), mode='wb')

  edit_patch = sbox.get_tempname('edit.patch')
  svntest.main.file_write(edit_patch, ''.join(diff_edit), mode='wb')

  rm_patch = sbox.get_tempname('rm.patch')
  svntest.main.file_write(rm_patch, ''.join(diff_rm), mode='wb')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'eicar.com' : Item(status='RM', wc_rev=3)
  })
  expected_output = svntest.wc.State(wc_dir, {
    'eicar.com' : Item(status='A ')
  })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'eicar.com' : Item(contents=eicar_data,
                       props={'svn:mime-type': 'application/octet-stream',
                              'svn:executable': '*'}),
  })
  expected_skip = svntest.wc.State(wc_dir, {})
  svntest.actions.run_and_verify_patch(wc_dir, add_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # And repeat
  expected_output.tweak('eicar.com', status='GG')
  svntest.actions.run_and_verify_patch(wc_dir, add_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # Now apply the edit
  expected_output.tweak('eicar.com', status='UU')
  expected_disk.tweak('eicar.com',
                      props={'svn:mime-type': 'application/octet-stream'},
                      contents=other_data)
  svntest.actions.run_and_verify_patch(wc_dir, edit_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # And repeat
  expected_output.tweak('eicar.com', status='GG')
  svntest.actions.run_and_verify_patch(wc_dir, edit_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # Now apply the edit
  expected_output.tweak('eicar.com', status='D ')
  expected_disk.remove('eicar.com')
  expected_status.tweak('eicar.com', status='D ')
  svntest.actions.run_and_verify_patch(wc_dir, rm_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # And repeat
  expected_output.tweak('eicar.com', status='GG')
  svntest.actions.run_and_verify_patch(wc_dir, rm_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  #And reverse
  expected_output.tweak('eicar.com', status='A ')
  expected_disk.add({
    'eicar.com' : Item(contents=other_data,
                       props={'svn:mime-type': 'application/octet-stream'}),
  })
  expected_status.tweak('eicar.com', status='RM')
  svntest.actions.run_and_verify_patch(wc_dir, rm_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

  # Repeat
  expected_output.tweak('eicar.com', status='GG')
  svntest.actions.run_and_verify_patch(wc_dir, rm_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

  # And reverse the edit
  expected_output.tweak('eicar.com', status='UU')
  expected_disk.tweak('eicar.com', contents=eicar_data,
                      props={'svn:mime-type': 'application/octet-stream',
                              'svn:executable': '*'})
  svntest.actions.run_and_verify_patch(wc_dir, edit_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')
  # Repeat
  expected_output.tweak('eicar.com', status='GG')
  svntest.actions.run_and_verify_patch(wc_dir, edit_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

  # And the add
  expected_output.tweak('eicar.com', status='D ')
  expected_disk.remove('eicar.com')
  expected_status.tweak('eicar.com', status='D ')
  svntest.actions.run_and_verify_patch(wc_dir, add_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

  # And a final repeat
  expected_output.tweak('eicar.com', status='GG')
  svntest.actions.run_and_verify_patch(wc_dir, add_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

def patch_git_symlink(sbox):
  "patch a git symlink"

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

  patch_add = [
    'diff --git a/link-to-iota b/link-to-iota\n',
    'new file mode 120000\n',
    'index 0000000..3ef26e4\n',
    '--- /dev/null\n',
    '+++ b/link-to-iota\n',
    '@@ -0,0 +1 @@\n',
    '+iota\n',
    '\ No newline at end of file\n',
  ]

  patch_edit = [
    'diff --git a/link-to-iota b/link-to-iota\n',
    'index 3ef26e4..33e5b38 120000\n',
    '--- a/link-to-iota\n',
    '+++ b/link-to-iota\n',
    '@@ -1 +1 @@\n',
    '-iota\n',
    '\ No newline at end of file\n',
    '+A/mu\n',
    '\ No newline at end of file\n',
  ]

  patch_to_file = [
    'diff --git a/link-to-iota b/link-to-iota\n',
    'deleted file mode 120000\n',
    'index 33e5b38..0000000\n',
    '--- a/link-to-iota\n',
    '+++ /dev/null\n',
    '@@ -1 +0,0 @@\n',
    '-A/mu\n',
    '\ No newline at end of file\n',
    'diff --git a/link-to-iota b/link-to-iota\n',
    'new file mode 100644\n',
    'index 0000000..1b130bf\n',
    '--- /dev/null\n',
    '+++ b/link-to-iota\n',
    '@@ -0,0 +1 @@\n',
    '+This is a real file\n',
  ]

  add_patch = sbox.get_tempname('add.patch')
  svntest.main.file_write(add_patch, ''.join(patch_add), mode='wb')

  edit_patch = sbox.get_tempname('edit.patch')
  svntest.main.file_write(edit_patch, ''.join(patch_edit), mode='wb')

  to_file_patch = sbox.get_tempname('to_file.patch')
  svntest.main.file_write(to_file_patch, ''.join(patch_to_file), mode='wb')


  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'link-to-iota'      : Item(status='A ', wc_rev='-'),
  })
  expected_output = svntest.wc.State(wc_dir, {
    'link-to-iota'      : Item(status='A '),
  })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'link-to-iota'      : Item(contents="This is the file 'iota'.\n",
                               props={'svn:special': '*'}),
  })
  if not svntest.main.is_posix_os():
    expected_disk.tweak('link-to-iota', contents='link iota')
  expected_skip = svntest.wc.State(wc_dir, {})

  svntest.actions.run_and_verify_patch(wc_dir, add_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # And again
  expected_output.tweak('link-to-iota', status='GG')
  svntest.actions.run_and_verify_patch(wc_dir, add_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # Now tweak the link
  expected_output.tweak('link-to-iota', status='U ')
  if svntest.main.is_posix_os():
    expected_disk.tweak('link-to-iota', contents="This is the file 'mu'.\n")
  else:
    expected_disk.tweak('link-to-iota', contents='link A/mu')
  svntest.actions.run_and_verify_patch(wc_dir, edit_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # And again
  expected_output.tweak('link-to-iota', status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, edit_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # And replace the link with a file
  expected_output.tweak('link-to-iota', status='A ', prev_status='D ')
  expected_disk.tweak('link-to-iota', contents="This is a real file\n",
                      props={})
  svntest.actions.run_and_verify_patch(wc_dir, to_file_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # And again - Delete can't be applied
  expected_output.tweak('link-to-iota', status='G ', prev_status='C ')
  expected_disk.add({
    'link-to-iota.svnpatch.rej': Item(
                     contents='--- link-to-iota\n'
                              '+++ /dev/null\n'
                              '@@ -1,1 +0,0 @@\n'
                              '-A/mu\n'
                              '\\ No newline at end of file\n'),
  })
  svntest.actions.run_and_verify_patch(wc_dir, to_file_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

def patch_like_git_symlink(sbox):
  "patch like a git symlink"

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

  patch_add = [
    'diff --git a/link-to-iota b/link-to-iota\n',
    'new file mode 100000\n',
    'index 0000000..3ef26e4\n',
    '--- /dev/null\n',
    '+++ b/link-to-iota\n',
    '@@ -0,0 +1 @@\n',
    '+iota\n',
    '\ No newline at end of file\n',
  ]

  patch_edit = [
    'diff --git a/link-to-iota b/link-to-iota\n',
    'index 3ef26e4..33e5b38 100000\n',
    '--- a/link-to-iota\n',
    '+++ b/link-to-iota\n',
    '@@ -1 +1 @@\n',
    '-iota\n',
    '\ No newline at end of file\n',
    '+A/mu\n',
    '\ No newline at end of file\n',
  ]

  patch_to_file = [
    'diff --git a/link-to-iota b/link-to-iota\n',
    'deleted file mode 100000\n',
    'index 33e5b38..0000000\n',
    '--- a/link-to-iota\n',
    '+++ /dev/null\n',
    '@@ -1 +0,0 @@\n',
    '-A/mu\n',
    '\ No newline at end of file\n',
    'diff --git a/link-to-iota b/link-to-iota\n',
    'new file mode 100644\n',
    'index 0000000..1b130bf\n',
    '--- /dev/null\n',
    '+++ b/link-to-iota\n',
    '@@ -0,0 +1 @@\n',
    '+This is a real file\n',
  ]

  add_patch = sbox.get_tempname('add.patch')
  svntest.main.file_write(add_patch, ''.join(patch_add), mode='wb')

  edit_patch = sbox.get_tempname('edit.patch')
  svntest.main.file_write(edit_patch, ''.join(patch_edit), mode='wb')

  to_file_patch = sbox.get_tempname('to_file.patch')
  svntest.main.file_write(to_file_patch, ''.join(patch_to_file), mode='wb')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'link-to-iota'      : Item(status='A ', wc_rev='-'),
  })
  expected_output = svntest.wc.State(wc_dir, {
    'link-to-iota'      : Item(status='A '),
  })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'link-to-iota'      : Item(contents="iota"),
  })
  expected_skip = svntest.wc.State(wc_dir, {})

  svntest.actions.run_and_verify_patch(wc_dir, add_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # And again
  expected_output.tweak('link-to-iota', status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, add_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # Now tweak the link
  expected_output.tweak('link-to-iota', status='U ')
  expected_disk.tweak('link-to-iota', contents='A/mu')
  svntest.actions.run_and_verify_patch(wc_dir, edit_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # And again
  expected_output.tweak('link-to-iota', status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, edit_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # And replace the link with a file
  expected_output.tweak('link-to-iota', status='U ')
  expected_output.tweak('link-to-iota', status='A ', prev_status='D ')
  expected_disk.tweak('link-to-iota', contents="This is a real file\n")
  svntest.actions.run_and_verify_patch(wc_dir, to_file_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # And again - Delete can't be applied
  expected_output.tweak('link-to-iota', status='G ', prev_status='C ')
  expected_disk.add({
    'link-to-iota.svnpatch.rej': Item(
                     contents='--- link-to-iota\n'
                              '+++ /dev/null\n'
                              '@@ -1,1 +0,0 @@\n'
                              '-A/mu\n'
                              '\\ No newline at end of file\n'),
  })
  svntest.actions.run_and_verify_patch(wc_dir, to_file_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

def patch_symlink_changes(sbox):
  "patch symlink changes"

  sbox.build()
  wc_dir = sbox.wc_dir
  strip_count = wc_dir.count(os.path.sep)+1

  os.remove(sbox.ospath('iota'))
  sbox.simple_symlink('A/B/E/beta', 'iota')
  sbox.simple_propset('svn:special', 'X', 'iota')

  _, diff_tolink, _ = svntest.actions.run_and_verify_svn(None, [],
                                                         'diff', wc_dir)

  _, git_tolink, _ = svntest.actions.run_and_verify_svn(None, [],
                                                         'diff', wc_dir, '--git')

  sbox.simple_commit()

  os.remove(sbox.ospath('iota'))
  sbox.simple_symlink('A/B/E/alpha', 'iota')

  _, diff_changelink, _ = svntest.actions.run_and_verify_svn(None, [],
                                                            'diff', wc_dir)

  _, git_changelink, _ = svntest.actions.run_and_verify_svn(None, [],
                                                            'diff', wc_dir, '--git')

  tolink_patch = sbox.get_tempname('tolink.patch')
  svntest.main.file_write(tolink_patch, ''.join(diff_tolink), mode='wb')

  git_tolink_patch = sbox.get_tempname('git_tolink.patch')
  svntest.main.file_write(git_tolink_patch, ''.join(git_tolink), mode='wb')

  changelink_patch = sbox.get_tempname('changelink.patch')
  svntest.main.file_write(changelink_patch, ''.join(diff_changelink), mode='wb')

  git_changelink_patch = sbox.get_tempname('git_changelink.patch')
  svntest.main.file_write(git_changelink_patch, ''.join(git_changelink), mode='wb')

  sbox.simple_revert('iota')
  sbox.simple_update('', 1)

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('iota', status='MM')
  expected_output = svntest.wc.State(wc_dir, {
    'iota'      : Item(status='UU'),
  })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('iota', props={'svn:special': '*'})
  expected_skip = svntest.wc.State(wc_dir, {})

  if svntest.main.is_posix_os():
    expected_disk.tweak('iota', contents="This is the file 'beta'.\n")
  else:
    expected_disk.tweak('iota', contents="link A/B/E/beta")

  # Turn into link
  svntest.actions.run_and_verify_patch(wc_dir, tolink_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count)

  # And in git style
  sbox.simple_revert('iota')
  svntest.actions.run_and_verify_patch(wc_dir, git_tolink_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # Retry
  expected_output.tweak('iota', status='GG')
  svntest.actions.run_and_verify_patch(wc_dir, tolink_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count)
  svntest.actions.run_and_verify_patch(wc_dir, git_tolink_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  sbox.simple_update('', 2) # Go to r2.
  sbox.simple_revert('iota')
  expected_status.tweak(wc_rev=2)

  # Turn back into files
  expected_output.tweak('iota', status='UU')
  expected_disk.tweak('iota', props={}, contents="This is the file 'iota'.\n")
  svntest.actions.run_and_verify_patch(wc_dir, tolink_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count,
                                       '--reverse-diff')

  # And in git style
  sbox.simple_revert('iota')
  svntest.actions.run_and_verify_patch(wc_dir, git_tolink_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

  # Retry
  expected_output.tweak('iota', status='GG')
  svntest.actions.run_and_verify_patch(wc_dir, tolink_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count,
                                       '--reverse-diff')
  svntest.actions.run_and_verify_patch(wc_dir, git_tolink_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

  # And now just tweak the link
  expected_output.tweak('iota', status='U ')
  expected_disk.tweak('iota', props={'svn:special': '*'})
  expected_status.tweak('iota', status='M ')

  if svntest.main.is_posix_os():
    expected_disk.tweak('iota', contents="This is the file 'alpha'.\n")
  else:
    expected_disk.tweak('iota', contents="link A/B/E/alpha")

  sbox.simple_revert('iota')
  svntest.actions.run_and_verify_patch(wc_dir, changelink_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count)

  # And in git style
  sbox.simple_revert('iota')
  svntest.actions.run_and_verify_patch(wc_dir, git_changelink_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # Retry
  expected_output.tweak('iota', status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, changelink_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count)
  svntest.actions.run_and_verify_patch(wc_dir, git_changelink_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

def patch_add_one_line(sbox):
  "patch add just one line"

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

  diff = [
    # This is a normal unified diff
    "Index: A/B/E/alpha",
    "===================================================================",
    "--- A/B/E/alpha\t(revision 1)",
    "+++ A/B/E/alpha\t(working copy)",
    "@@ -1 +1,2 @@",
    " This is the file 'alpha'.",
    "+This is the file 'alpha'.",

    "",

    # This diff is hand crafted, as a generated diff would add the line at
    # the end
    "Index: A/B/E/beta",
    "===================================================================",
    "--- A/B/E/beta\t(revision 1)",
    "+++ A/B/E/beta\t(working copy)",
    "@@ -1 +1,2 @@",
    "+This is the file 'beta'.",
    " This is the file 'beta'.",
    ""
  ]

  recurse_patch = sbox.get_tempname('recurse.patch')
  svntest.main.file_write(recurse_patch, '\n'.join(diff), mode='wb')

  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/B/E/alpha', 'A/B/E/beta', status='M ')
  expected_output = svntest.wc.State(wc_dir, {
    'A/B/E/alpha'   : Item(status='U '),
    'A/B/E/beta'    : Item(status='U '),
  })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/B/E/alpha', contents="This is the file 'alpha'.\nThis is the file 'alpha'.\n")
  expected_disk.tweak('A/B/E/beta', contents="This is the file 'beta'.\nThis is the file 'beta'.\n")
  expected_skip = svntest.wc.State(wc_dir, {})

  svntest.actions.run_and_verify_patch(wc_dir, recurse_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # Retry
  expected_output.tweak(status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, recurse_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  sbox.simple_append('A/B/E/alpha',
                     "This is the file 'alpha'.\n")
  sbox.simple_append('A/B/E/beta',
                     "This is the file 'beta'.\n")

  # But can we remove the line? - Yes
  expected_output.tweak(status='U ')
  svntest.actions.run_and_verify_patch(wc_dir, recurse_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

  # Once more?
  expected_disk = svntest.main.greek_state.copy()
  expected_status.tweak('A/B/E/alpha', 'A/B/E/beta', status='  ')
  svntest.actions.run_and_verify_patch(wc_dir, recurse_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

  # And the last lines? - No...
  expected_output.tweak(status='G ')
  svntest.actions.run_and_verify_patch(wc_dir, recurse_patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

def patch_with_mergeinfo(sbox):
  "patch with mergeinfo"

  sbox.build()
  wc_dir = sbox.wc_dir
  strip_count = wc_dir.count(os.path.sep)+1

  sbox.simple_copy('A/B/E', 'E')
  sbox.simple_append('A/B/E/alpha', 'extra\nlines\n')
  sbox.simple_commit()

  sbox.simple_propset('a', 'A', 'E') # 'a' < 'svn:mergeinfo'
  sbox.simple_propset('z', 'Z', 'E') # 'z' > 'svn:mergeinfo'

  svntest.actions.run_and_verify_svn(None, [],
                                     'merge', '^/A/B/E', sbox.ospath('E'))

  _, diff, _ = svntest.actions.run_and_verify_svn(None, [],
                                                  'diff', wc_dir)

  sbox.simple_revert('E', 'E/alpha')

  patch = sbox.get_tempname('recurse.patch')
  svntest.main.file_write(patch, ''.join(diff), mode='wb')

  expected_output = wc.State(wc_dir, {
    'E'                 : Item(status=' U'),
    'E/alpha'           : Item(status='U '),
  })
  expected_skip = wc.State(wc_dir, {})
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'E'                 : Item(status=' M', wc_rev='2'),
    'E/alpha'           : Item(status='M ', wc_rev='2'),
    'E/beta'            : Item(status='  ', wc_rev='2'),
  })
  expected_status.tweak('A/B/E/alpha', wc_rev=2)
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/B/E/alpha', contents="This is the file 'alpha'.\nextra\nlines\n")
  expected_disk.add({
    'E'                 : Item(props={'a': 'A',
                                      # We can't apply 'svn:mergeinfo' (yet)
                                      'z': 'Z'}),
    'E/beta'            : Item(contents="This is the file 'beta'.\n"),
    'E/alpha'           : Item(contents="This is the file 'alpha'.\nextra\nlines\n"),
  })

  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count)

def patch_move_and_change(sbox):
  "patch move and change"

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

  sbox.simple_append('A/B/E/alpha', 'extra\nlines\n')
  sbox.simple_propset('k', 'v', 'A/B/E/alpha')

  sbox.simple_move('A/B/E/alpha', 'alpha')

  _, diff, _ = svntest.actions.run_and_verify_svn(None, [],
                                                  'diff', wc_dir, '--git')

  patch = sbox.get_tempname('move_and_change.patch')
  svntest.main.file_write(patch, ''.join(diff), mode='wb')

  # Running the diff reversed doesn't work...
  # We perform the add before reverting the move...
  expected_output = wc.State(wc_dir, {
    'A/B/E/alpha' : Item(status='A '),
  })
  expected_skip = wc.State(wc_dir, {
    'alpha' : Item(verb='Skipped'),
  })
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.tweak('A/B/E/alpha', status='R ',
                        moved_to='alpha')
  expected_status.add({
    'alpha'             : Item(status='A ', copied='+',
                               moved_from='A/B/E/alpha', wc_rev='-'),
  })
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'alpha' : Item(contents="This is the file 'alpha'.\nextra\nlines\n",
                   props={'k': 'v'}),
  })
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

  # Ok, let's remove the 'delete' portion and try in a clean WC
  n = diff.index('Index: %s\n' % sbox.path('alpha'))
  diff = diff[n:]
  svntest.main.file_write(patch, ''.join(diff), mode='wb')

  sbox.simple_revert('A/B/E/alpha', 'alpha')

  expected_output = wc.State(wc_dir, {
    'A/B/E/alpha' : Item(status='D '),
    'alpha'       : Item(status='A '),
  })
  expected_skip = wc.State(wc_dir, {})
  expected_disk.remove('A/B/E/alpha')
  expected_status.tweak('A/B/E/alpha', status='D ')
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # Retry
  expected_output = svntest.wc.State(wc_dir, {
    'alpha'             : Item(status='GG'),
  })
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True)

  # And now reverse
  expected_output = wc.State(wc_dir, {
    'alpha'       : Item(status='D '),
    'A/B/E/alpha' : Item(status='A '),
  })
  expected_disk.remove('alpha')
  expected_disk.add({
    'A/B/E/alpha'       : Item(contents="This is the file 'alpha'.\n"),
  })
  expected_status.remove('alpha')
  expected_status.tweak('A/B/E/alpha', status='  ', moved_to=None)
  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--reverse-diff')

@Issue(4609)
def missing_trailing_context(sbox):
  "missing trailing context"

  sbox.build()
  wc_dir = sbox.wc_dir

  sbox.simple_append('A/mu',
                     'a\n'
                     'b\n'
                     'c\n'
                     'd\n'
                     'e\n',
                     truncate=True)
  sbox.simple_commit()
  sbox.simple_update()

  # The hunk is expected to have two lines of trailing context but
  # only has one.
  unidiff_patch = [
    "Index: A/mu\n"
    "===================================================================\n",
    "--- A/mu\t(revision 2)\n",
    "+++ A/mu\t(working copy)\n",
    "@@ -1,5 +1,5 @@\n",
    " a\n",
    " b\n",
    "-c\n",
    "+cc\n",
    " d\n",
  ]
  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch), 'wb')

  # GNU patch will apply the hunk with fuzz 1 and modify only the 'c' line.
  # Our patch file finds the length mismatch and applies a penalty.
  expected_output = [
    'U         %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk @@ -1,4 +1,4 @@ with fuzz 1\n',
  ]
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/mu', contents =
                     'a\n'
                     'b\n'
                     'cc\n'
                     'd\n'
                     'e\n')
  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
  expected_status.tweak('A/mu', status='M ')
  expected_skip = wc.State('', { })
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

  # Try reverse patch
  expected_disk.tweak('A/mu', contents =
                     'a\n'
                     'b\n'
                     'c\n'
                     'd\n'
                     'e\n')
  expected_status.tweak('A/mu', status='  ')
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], False, True, '--reverse-diff')

  # The hunk is expected to have two lines of trailing context but
  # only has one.
  unidiff_patch = [
    "Index: A/mu\n"
    "===================================================================\n",
    "--- A/mu\t(revision 2)\n",
    "+++ A/mu\t(working copy)\n",
    "@@ -1,4 +1,4 @@\n",
    " a\n",
    " b\n",
    "-c\n",
    "+cc\n",
    " d\n",
    " e\n",
  ]
  patch_file_path = sbox.get_tempname('my2.patch')
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch), 'wb')

  expected_output = [
    'U         %s\n' % sbox.ospath('A/mu'),
    '>         applied hunk @@ -1,5 +1,5 @@ with fuzz 1\n',
  ]
  expected_disk.tweak('A/mu', contents =
                     'a\n'
                     'b\n'
                     'cc\n'
                     'd\n'
                     'e\n')
  expected_status.tweak('A/mu', status='M ')
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

  # Try reverse patch
  expected_disk.tweak('A/mu', contents =
                     'a\n'
                     'b\n'
                     'c\n'
                     'd\n'
                     'e\n')
  expected_status.tweak('A/mu', status='  ')
  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], False, True, '--reverse-diff')

def patch_missed_trail(sbox):
  "apply a patch to an empty file"

  sbox.build()
  wc_dir = sbox.wc_dir

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join([
  # Add a line to a file with just '\n' with bad header (should be +1,2)
    "Index: lf.txt\n",
    "===================================================================\n",
    "--- lf.txt\t(revision 2)\n",
    "+++ lf.txt\t(working copy)\n",
    "@@ -1 +1 @@\n",
    "\n"
    "+replacement\n",
  ]))

  sbox.simple_add_text('\n', 'lf.txt')
  sbox.simple_commit()

  expected_output = [
    'U         %s\n' % sbox.ospath('lf.txt'),
    '>         applied hunk @@ -1,1 +1,2 @@ with fuzz 1\n',
  ]

  # Current result: lf.txt patched ok, new created, empty succeeds with offset.
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'lf.txt'            : Item(contents="\nreplacement\n"),
  })
  expected_skip = wc.State(wc_dir, {})
  expected_status = None

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip)

@XFail()
def patch_merge(sbox):
  "patching a specific merge"

  sbox.build()
  wc_dir = sbox.wc_dir
  repo_url = sbox.repo_url

  sbox.simple_add_text('A\n'
                       'B\n'
                       'C\n'
                       'J\n'
                       'K\n'
                       'L', 'new.txt')
  sbox.simple_commit()

  remote_patch = sbox.get_tempname('remote.patch')
  svntest.main.file_write(remote_patch,
      '--- new.txt\t(revision 6)\n'
      '+++ new.txt\t(revision 7)\n'
      '@@ -1,6 +1,9 @@\n'
      ' A\n'
      ' B\n'
      '-C\n'
      '+ C\n'
      '+D\n'
      '+E\n'
      '+F\n'
      ' J\n'
      ' K\n'
      ' L\n'
      '\ No newline at end of file', mode='wb')

  expected_skip = wc.State('', { })
  expected_output = wc.State(wc_dir, {
      'new.txt' : Item(status='U '),
    })
  svntest.actions.run_and_verify_patch(wc_dir, remote_patch,
                                       expected_output, None,
                                       None, expected_skip)
  sbox.simple_commit()
  sbox.simple_update(revision=2)

  local_patch = sbox.get_tempname('local.patch')
  svntest.main.file_write(local_patch,
      '--- new.txt\t(revision 3)\n'
      '+++ new.txt\t(revision 4)\n'
      '@@ -1,6 +1,9 @@\n'
      ' A\n'
      ' B\n'
      ' C\n'
      '+D\n'
      '+E\n'
      '+F\n'
      ' J\n'
      ' K\n'
      ' L\n'
      '\ No newline at end of file', mode='wb')

  svntest.actions.run_and_verify_patch(wc_dir, local_patch,
                                       expected_output, None,
                                       None, expected_skip)

  # Currently we see D E F doubled, that is certainly bad behavior.
  # I could imaging that just applying the 'C' line change would be ok,
  # but most likely a text conflict is the proper thing to do here.
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.add({
    'new.txt' : Item(contents='A\n'
                              'B\n'
                              ' C\n'
                              'D\n'
                              'E\n'
                              'F\n'
                              #'D\n' # Doubled???
                              #'E\n' # Doubled???
                              #'F\n' # Doubled???
                              'J\n'
                              'K\n'
                              'L')})
  expected_output.tweak('new.txt', status='G ')
  svntest.actions.run_and_verify_update(wc_dir, expected_output, expected_disk,
                                        None, [])

  # Revert to base position
  sbox.simple_revert('new.txt')
  sbox.simple_update(revision=2)

  # And now do the same thing as a merge instead of an update
  expected_output.tweak('new.txt', status='U ')
  svntest.actions.run_and_verify_patch(wc_dir, local_patch,
                                       expected_output, None,
                                       None, expected_skip)

  expected_output.tweak('new.txt', status='G ')
  svntest.actions.run_and_verify_merge(wc_dir, 2, 3, repo_url, repo_url,
                                       expected_output, None, None,
                                       expected_disk, None,
                                       expected_skip)

def patch_mergeinfo_in_regular_prop_format(sbox):
  "patch mergeinfo in regular prop format"

  sbox.build()
  wc_dir = sbox.wc_dir
  strip_count = wc_dir.count(os.path.sep)+1

  sbox.simple_copy('A/B/E', 'E')
  sbox.simple_append('A/B/E/alpha', 'extra\nlines\n')
  sbox.simple_commit()

  sbox.simple_propset('a', 'A', 'E') # 'a' < 'svn:mergeinfo'
  sbox.simple_propset('z', 'Z', 'E') # 'z' > 'svn:mergeinfo'

  svntest.actions.run_and_verify_svn(None, [],
                                     'merge', '^/A/B/E', sbox.ospath('E'))
  # Rename 'svn:mergeinfo' to 'svn_mergeinfo' so that 'diff' doesn't
  # pretty-print it; then rename it back before we run it through 'patch'.
  # (Alternatively, we could disable pretty-printing when we implement a
  # command-line switch to do so.)
  mergeinfo_value = sbox.simple_propget('svn:mergeinfo', 'E')
  sbox.simple_propdel('svn:mergeinfo', 'E')
  sbox.simple_propset('svn_mergeinfo', mergeinfo_value, 'E')

  _, diff, _ = svntest.actions.run_and_verify_svn(None, [],
                                                  'diff', wc_dir)
  diff = re.sub('svn_mergeinfo', 'svn:mergeinfo', ''.join(diff))

  sbox.simple_revert('E', 'E/alpha')

  patch = sbox.get_tempname('recurse.patch')
  svntest.main.file_write(patch, diff, mode='wb')

  expected_output = wc.State(wc_dir, {
    'E'                 : Item(status=' U'),
    'E/alpha'           : Item(status='U '),
  })
  expected_skip = wc.State(wc_dir, {})
  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
  expected_status.add({
    'E'                 : Item(status=' M', wc_rev='2'),
    'E/alpha'           : Item(status='M ', wc_rev='2'),
    'E/beta'            : Item(status='  ', wc_rev='2'),
  })
  expected_status.tweak('A/B/E/alpha', wc_rev=2)
  expected_disk = svntest.main.greek_state.copy()
  expected_disk.tweak('A/B/E/alpha', contents="This is the file 'alpha'.\nextra\nlines\n")
  expected_disk.add({
    'E'                 : Item(props={'a': 'A',
                                      # here is the correctly patched mergeinfo
                                      'svn:mergeinfo': '/A/B/E:2',
                                      'z': 'Z'}),
    'E/beta'            : Item(contents="This is the file 'beta'.\n"),
    'E/alpha'           : Item(contents="This is the file 'alpha'.\nextra\nlines\n"),
  })

  svntest.actions.run_and_verify_patch(wc_dir, patch,
                                       expected_output, expected_disk,
                                       expected_status, expected_skip,
                                       [], True, True,
                                       '--strip', strip_count)

@XFail()
def patch_empty_prop(sbox):
  "patch empty prop"
  sbox.build(empty=True)
  was_cwd = os.getcwd()
  os.chdir(sbox.wc_dir)
  sbox.wc_dir = ''
  wc_dir = ''

  # start with a file with an empty prop
  sbox.simple_add_text('', 'f')
  sbox.simple_propset('p', '', 'f')
  sbox.simple_commit()

  # a patch that modifies the prop to a non-empty value
  unidiff_patch = [
    "--- f\n",
    "+++ f\n",
    "\n",
    "Property changes on: f\n",
    "___________________________________________________________________\n",
    "Modified: p\n",
    "## -0,0 +1 ##\n",
    "+v\n",
  ]

  trailing_eol = False
  if trailing_eol:
    value = "v\n"
  else:
    value = "v"
    unidiff_patch += ['\ No newline at end of property\n']

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join(unidiff_patch), 'wb')

  expected_output = [
    ' U        %s\n' % sbox.ospath('f'),
  ]

  expected_disk = svntest.wc.State(wc_dir, {})
  expected_disk.add({'f': Item(props={'p' : value})})
  expected_status = svntest.wc.State(wc_dir, {})
  expected_status.add({'f': Item(status=' M')})
  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       1, # check-props
                                       False, # dry-run
                                       )

  svntest.actions.check_prop('p', wc_dir, [value.encode()])

  os.chdir(was_cwd)

# Test that 'patch' can apply a patch that modifies properties on the root
# of a WC and/or the root of a repository. This test uses the format of
# diff output produced by 'svn diff --git' in svn <= 1.10.0: paths are given
# as 'a/' and 'b/' and 'Property changes on: ' (with no following '.').
#
# See dev@ email thread 2018-07-09 from Dmitry Pavlenko,
# '[PATCH] can't "svn patch" working copy root if the patch is in --git format',
# https://lists.apache.org/thread.html/d1d9811ca36fac8cabb9339634840099e22811beac505be2ea59f19f@%3Cdev.subversion.apache.org%3E
@XFail()
def patch_git_wcroot(sbox):
  "patch working copy root"
  sbox.build(empty=True)
  wc_dir = sbox.wc_dir

  git_patch = [ "Index: .\n",
                "===================================================================\n",
                "diff --git a/ b/\n",
                "--- a/	(revision 0)\n",
                "+++ b/	(working copy)\n",
                "Property changes on: \n",
                "___________________________________________________________________\n",
                "Added: p\n",
                "## -0,0 +1 ##\n",
                "+v\n",
                "\\ No newline at end of property\n",
  ]
  value = 'v'

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join(git_patch), 'wb')

  expected_output = wc.State(wc_dir, {
    '.'                 : Item(status=' U'),
  })
  expected_disk = svntest.wc.State('', {})
  expected_disk.add({'': Item(props={'p' : value })})
  expected_status = svntest.wc.State(wc_dir, {})
  expected_status.add({'': Item(status=' M', wc_rev='0')})
  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       True, # check-props
                                       False, # dry-run
                                       )

  svntest.actions.check_prop('p', wc_dir, [value.encode()])

# Test that 'patch' can apply a patch that modifies properties on the root
# of a WC and/or the root of a repository. This test performs a round-trip
# through 'diff --git' and 'patch' rather than assuming any particular
# variant of the patch format.
#
# See dev@ email thread 2018-07-09 from Dmitry Pavlenko,
# '[PATCH] can't "svn patch" working copy root if the patch is in --git format',
# https://lists.apache.org/thread.html/d1d9811ca36fac8cabb9339634840099e22811beac505be2ea59f19f@%3Cdev.subversion.apache.org%3E
@XFail()
def patch_git_wcroot2(sbox):
  "patch working copy root"
  sbox.build(empty=True)
  wc_dir = sbox.wc_dir

  value = 'v'
  sbox.simple_propset('p', value, '')
  exit_code, git_patch, err_output = svntest.main.run_svn(None, 'diff',
                                                          '--git', wc_dir)

  patch_file_path = sbox.get_tempname('my.patch')
  svntest.main.file_write(patch_file_path, ''.join(git_patch), 'wb')

  sbox.simple_revert('')

  expected_output = wc.State(wc_dir, {
    '.'                 : Item(status=' U'),
  })
  expected_disk = svntest.wc.State('', {})
  expected_disk.add({'': Item(props={'p' : value })})
  expected_status = svntest.wc.State(wc_dir, {})
  expected_status.add({'': Item(status=' M', wc_rev='0')})
  expected_skip = wc.State('', { })

  svntest.actions.run_and_verify_patch(wc_dir, patch_file_path,
                                       expected_output,
                                       expected_disk,
                                       expected_status,
                                       expected_skip,
                                       None, # expected err
                                       True, # check-props
                                       False, # dry-run
                                       )

  svntest.actions.check_prop('p', wc_dir, [value.encode()])

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

# list all tests here, starting with None:
test_list = [ None,
              patch,
              patch_absolute_paths,
              patch_offset,
              patch_chopped_leading_spaces,
              patch_strip1,
              patch_no_index_line,
              patch_add_new_dir,
              patch_remove_empty_dirs,
              patch_reject,
              patch_keywords,
              patch_with_fuzz,
              patch_reverse,
              patch_no_svn_eol_style,
              patch_with_svn_eol_style,
              patch_with_svn_eol_style_uncommitted,
              patch_with_ignore_whitespace,
              patch_replace_locally_deleted_file,
              patch_no_eol_at_eof,
              patch_with_properties,
              patch_same_twice,
              patch_dir_properties,
              patch_add_path_with_props,
              patch_prop_offset,
              patch_prop_with_fuzz,
              patch_git_empty_files,
              patch_old_target_names,
              patch_reverse_revert,
              patch_strip_cwd,
              patch_set_prop_no_eol,
              patch_add_symlink,
              patch_moved_away,
              patch_lacking_trailing_eol,
              patch_deletes_prop,
              patch_reversed_add_with_props,
              patch_reversed_add_with_props2,
              patch_dev_null,
              patch_delete_and_skip,
              patch_target_no_eol_at_eof,
              patch_add_and_delete,
              patch_git_with_index_line,
              patch_change_symlink_target,
              patch_replace_dir_with_file_and_vv,
              single_line_mismatch,
              patch_empty_file,
              patch_apply_no_fuz,
              patch_lacking_trailing_eol_on_context,
              patch_with_custom_keywords,
              patch_git_rename,
              patch_hunk_avoid_reorder,
              patch_hunk_avoid_reorder2,
              patch_hunk_reorder,
              patch_hunk_overlap,
              patch_delete_modified,
              patch_closest,
              patch_symlink_traversal,
              patch_obstructing_symlink_traversal,
              patch_binary_file,
              patch_delete_nodes,
              patch_delete_missing_eol,
              patch_final_eol,
              patch_adds_executability_nocontents,
              patch_adds_executability_nocontents2,
              patch_adds_executability_yescontents,
              patch_deletes_executability,
              patch_ambiguous_executability_contradiction,
              patch_ambiguous_executability_consistent,
              patch_prop_madness,
              patch_empty_vs_delete,
              patch_add_remove_executable,
              patch_git_symlink,
              patch_like_git_symlink,
              patch_symlink_changes,
              patch_add_one_line,
              patch_with_mergeinfo,
              patch_move_and_change,
              missing_trailing_context,
              patch_missed_trail,
              patch_merge,
              patch_mergeinfo_in_regular_prop_format,
              patch_empty_prop,
              patch_git_wcroot,
              patch_git_wcroot2,
            ]

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


### End of file.
