You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by rh...@apache.org on 2015/11/30 11:24:23 UTC

svn commit: r1717223 [11/50] - in /subversion/branches/ra-git: ./ build/ build/ac-macros/ build/generator/ build/generator/templates/ contrib/hook-scripts/ notes/ notes/api-errata/1.9/ notes/move-tracking/ subversion/ subversion/bindings/ctypes-python/...

Modified: subversion/branches/ra-git/subversion/libsvn_client/patch.c
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/subversion/libsvn_client/patch.c?rev=1717223&r1=1717222&r2=1717223&view=diff
==============================================================================
--- subversion/branches/ra-git/subversion/libsvn_client/patch.c (original)
+++ subversion/branches/ra-git/subversion/libsvn_client/patch.c Mon Nov 30 10:24:16 2015
@@ -46,6 +46,7 @@
 #include "private/svn_eol_private.h"
 #include "private/svn_wc_private.h"
 #include "private/svn_dep_compat.h"
+#include "private/svn_diff_private.h"
 #include "private/svn_string_private.h"
 #include "private/svn_subr_private.h"
 #include "private/svn_sorts_private.h"
@@ -152,6 +153,9 @@ typedef struct prop_patch_target_t {
    * ### Should we use flags instead since we're not using all enum values? */
   svn_diff_operation_kind_t operation;
 
+  /* When true the property change won't be applied */
+  svn_boolean_t skipped;
+
   /* ### Here we'll add flags telling if the prop was added, deleted,
    * ### had_rejects, had_local_mods prior to patching and so on. */
 } prop_patch_target_t;
@@ -190,8 +194,8 @@ typedef struct patch_target_t {
   /* Path to the patched file. */
   const char *patched_path;
 
-  /* Hunks that are rejected will be written to this file. */
-  apr_file_t *reject_file;
+  /* Hunks that are rejected will be written to this stream. */
+  svn_stream_t *reject_stream;
 
   /* Path to the reject file. */
   const char *reject_path;
@@ -209,8 +213,8 @@ typedef struct patch_target_t {
   /* True if the target had to be skipped for some reason. */
   svn_boolean_t skipped;
 
-  /* True if the target has been filtered by the patch callback. */
-  svn_boolean_t filtered;
+  /* True if the reason for skipping is a local obstruction */
+  svn_boolean_t obstructed;
 
   /* True if at least one hunk was rejected. */
   svn_boolean_t had_rejects;
@@ -218,9 +222,14 @@ typedef struct patch_target_t {
   /* True if at least one property hunk was rejected. */
   svn_boolean_t had_prop_rejects;
 
-  /* True if the target file had local modifications before the
-   * patch was applied to it. */
-  svn_boolean_t local_mods;
+  /* True if at least one hunk was handled as already applied */
+  svn_boolean_t had_already_applied;
+
+  /* True if at least one property hunk was handled as already applied */
+  svn_boolean_t had_prop_already_applied;
+
+  /* The operation on the target as set in the patch file */
+  svn_diff_operation_kind_t operation;
 
   /* True if the target was added by the patch, which means that it did
    * not exist on disk before patching and has content after patching. */
@@ -229,10 +238,6 @@ typedef struct patch_target_t {
   /* True if the target ended up being deleted by the patch. */
   svn_boolean_t deleted;
 
-  /* True if the target ended up being replaced by the patch
-   * (i.e. a new file was added on top locally deleted node). */
-  svn_boolean_t replaced;
-
   /* Set if the target is supposed to be moved by the patch.
    * This applies to --git diffs which carry "rename from/to" headers. */
    const char *move_target_abspath;
@@ -255,6 +260,10 @@ typedef struct patch_target_t {
   /* A hash table of prop_patch_target_t objects keyed by property names. */
   apr_hash_t *prop_targets;
 
+  /* When TRUE, this patch uses the raw git symlink format instead of the
+     Subversion internal style format where links start with 'link '. */
+  svn_boolean_t git_symlink_format;
+
 } patch_target_t;
 
 
@@ -264,8 +273,58 @@ typedef struct patch_target_t {
 typedef struct patch_target_info_t {
   const char *local_abspath;
   svn_boolean_t deleted;
+  svn_boolean_t added;
 } patch_target_info_t;
 
+/* Check if LOCAL_ABSPATH is recorded as added in TARGETS_INFO */
+static svn_boolean_t
+target_is_added(const apr_array_header_t *targets_info,
+                const char *local_abspath,
+                apr_pool_t *scratch_pool)
+{
+  int i;
+
+  for (i = targets_info->nelts - 1; i >= 0; i--)
+  {
+    const patch_target_info_t *target_info =
+      APR_ARRAY_IDX(targets_info, i, const patch_target_info_t *);
+
+    const char *info = svn_dirent_skip_ancestor(target_info->local_abspath,
+                                                local_abspath);
+
+    if (info && !*info)
+      return target_info->added;
+    else if (info)
+      return FALSE;
+  }
+
+  return FALSE;
+}
+
+/* Check if LOCAL_ABSPATH or an ancestor is recorded as deleted in
+   TARGETS_INFO */
+static svn_boolean_t
+target_is_deleted(const apr_array_header_t *targets_info,
+                  const char *local_abspath,
+                  apr_pool_t *scratch_pool)
+{
+  int i;
+
+  for (i = targets_info->nelts - 1; i >= 0; i--)
+  {
+    const patch_target_info_t *target_info =
+      APR_ARRAY_IDX(targets_info, i, const patch_target_info_t *);
+
+    const char *info = svn_dirent_skip_ancestor(target_info->local_abspath,
+                                                local_abspath);
+
+    if (info)
+      return target_info->deleted;
+  }
+
+  return FALSE;
+}
+
 
 /* Strip STRIP_COUNT components from the front of PATH, returning
  * the result in *RESULT, allocated in RESULT_POOL.
@@ -371,18 +430,20 @@ obtain_eol_and_keywords_for_file(apr_has
  * Indicate in TARGET->SKIPPED whether the target should be skipped.
  * STRIP_COUNT specifies the number of leading path components
  * which should be stripped from target paths in the patch.
- * PROP_CHANGES_ONLY specifies whether the target path is allowed to have
- * only property changes, and no content changes (in which case the target
- * must be a directory).
+ * HAS_TEXT_CHANGES specifies whether the target path will have some text
+ * changes applied, implying that the target should be a file and not a
+ * directory.
  * Use RESULT_POOL for allocations of fields in TARGET.
  * Use SCRATCH_POOL for all other allocations. */
 static svn_error_t *
 resolve_target_path(patch_target_t *target,
                     const char *path_from_patchfile,
-                    const char *wcroot_abspath,
+                    const char *root_abspath,
                     int strip_count,
-                    svn_boolean_t prop_changes_only,
+                    svn_boolean_t has_text_changes,
+                    svn_boolean_t follow_moves,
                     svn_wc_context_t *wc_ctx,
+                    const apr_array_header_t *targets_info,
                     apr_pool_t *result_pool,
                     apr_pool_t *scratch_pool)
 {
@@ -394,8 +455,8 @@ resolve_target_path(patch_target_t *targ
   target->canon_path_from_patchfile = svn_dirent_internal_style(
                                         path_from_patchfile, result_pool);
 
-  /* We allow properties to be set on the wc root dir. */
-  if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0')
+  /* We can't handle text changes on the patch root dir. */
+  if (has_text_changes && target->canon_path_from_patchfile[0] == '\0')
     {
       /* An empty patch target path? What gives? Skip this. */
       target->skipped = TRUE;
@@ -412,14 +473,14 @@ resolve_target_path(patch_target_t *targ
 
   if (svn_dirent_is_absolute(stripped_path))
     {
-      target->local_relpath = svn_dirent_is_child(wcroot_abspath,
+      target->local_relpath = svn_dirent_is_child(root_abspath,
                                                   stripped_path,
                                                   result_pool);
 
       if (! target->local_relpath)
         {
           /* The target path is either outside of the working copy
-           * or it is the working copy itself. Skip it. */
+           * or it is the patch root itself. Skip it. */
           target->skipped = TRUE;
           target->local_abspath = NULL;
           target->local_relpath = stripped_path;
@@ -432,9 +493,9 @@ resolve_target_path(patch_target_t *targ
     }
 
   /* Make sure the path is secure to use. We want the target to be inside
-   * of the working copy and not be fooled by symlinks it might contain. */
+   * the locked tree and not be fooled by symlinks it might contain. */
   SVN_ERR(svn_dirent_is_under_root(&under_root,
-                                   &target->local_abspath, wcroot_abspath,
+                                   &target->local_abspath, root_abspath,
                                    target->local_relpath, result_pool));
 
   if (! under_root)
@@ -445,6 +506,13 @@ resolve_target_path(patch_target_t *targ
       return SVN_NO_ERROR;
     }
 
+  if (target_is_deleted(targets_info, target->local_abspath, scratch_pool))
+    {
+      target->locally_deleted = TRUE;
+      target->db_kind = svn_node_none;
+      return SVN_NO_ERROR;
+    }
+
   /* Skip things we should not be messing with. */
   err = svn_wc_status3(&status, wc_ctx, target->local_abspath,
                        result_pool, scratch_pool);
@@ -466,6 +534,7 @@ resolve_target_path(patch_target_t *targ
            status->conflicted)
     {
       target->skipped = TRUE;
+      target->obstructed = TRUE;
       return SVN_NO_ERROR;
     }
   else if (status->node_status == svn_wc_status_deleted)
@@ -484,18 +553,29 @@ resolve_target_path(patch_target_t *targ
 
   if (target->locally_deleted)
     {
-      const char *moved_to_abspath;
+      const char *moved_to_abspath = NULL;
+
+      if (follow_moves
+          && !target_is_added(targets_info, target->local_abspath,
+                              scratch_pool))
+        {
+          SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
+                                              wc_ctx, target->local_abspath,
+                                              result_pool, scratch_pool));
+        }
 
-      SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
-                                          wc_ctx, target->local_abspath,
-                                          result_pool, scratch_pool));
       if (moved_to_abspath)
         {
           target->local_abspath = moved_to_abspath;
-          target->local_relpath = svn_dirent_skip_ancestor(wcroot_abspath,
-                                                          moved_to_abspath);
-          SVN_ERR_ASSERT(target->local_relpath &&
-                         target->local_relpath[0] != '\0');
+          target->local_relpath = svn_dirent_skip_ancestor(root_abspath,
+                                                           moved_to_abspath);
+
+          if (!target->local_relpath || target->local_relpath[0] == '\0')
+            {
+              /* The target path is outside of the patch area. Skip it. */
+              target->skipped = TRUE;
+              return SVN_NO_ERROR;
+            }
 
           /* As far as we are concerned this target is not locally deleted. */
           target->locally_deleted = FALSE;
@@ -512,6 +592,22 @@ resolve_target_path(patch_target_t *targ
         }
     }
 
+#ifndef HAVE_SYMLINK
+  if (target->kind_on_disk == svn_node_file
+      && !target->is_symlink
+      && !target->locally_deleted
+      && status->prop_status != svn_wc_status_none)
+    {
+      const svn_string_t *value;
+
+      SVN_ERR(svn_wc_prop_get2(&value, wc_ctx, target->local_abspath,
+                               SVN_PROP_SPECIAL, scratch_pool, scratch_pool));
+
+      if (value)
+        target->is_symlink = TRUE;
+    }
+#endif
+
   return SVN_NO_ERROR;
 }
 
@@ -541,7 +637,7 @@ readline_prop(void *baton, svn_stringbuf
               svn_boolean_t *eof, apr_pool_t *result_pool,
               apr_pool_t *scratch_pool)
 {
-  prop_read_baton_t *b = (prop_read_baton_t *)baton;
+  prop_read_baton_t *b = baton;
   svn_stringbuf_t *str = NULL;
   const char *c;
   svn_boolean_t found_eof;
@@ -594,7 +690,7 @@ readline_prop(void *baton, svn_stringbuf
   while (c < b->value->data + b->value->len);
 
   if (eof)
-    *eof = found_eof;
+    *eof = found_eof && !(str && str->len > 0);
   *line = str;
 
   return SVN_NO_ERROR;
@@ -606,7 +702,8 @@ readline_prop(void *baton, svn_stringbuf
 static svn_error_t *
 tell_prop(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
 {
-  prop_read_baton_t *b = (prop_read_baton_t *)baton;
+  prop_read_baton_t *b = baton;
+
   *offset = b->offset;
   return SVN_NO_ERROR;
 }
@@ -616,7 +713,8 @@ tell_prop(void *baton, apr_off_t *offset
 static svn_error_t *
 seek_prop(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
 {
-  prop_read_baton_t *b = (prop_read_baton_t *)baton;
+  prop_read_baton_t *b = baton;
+
   b->offset = offset;
   return SVN_NO_ERROR;
 }
@@ -627,7 +725,8 @@ static svn_error_t *
 write_prop(void *baton, const char *buf, apr_size_t len,
            apr_pool_t *scratch_pool)
 {
-  svn_stringbuf_t *patched_value = (svn_stringbuf_t *)baton;
+  svn_stringbuf_t *patched_value = baton;
+
   svn_stringbuf_appendbytes(patched_value, buf, len);
   return SVN_NO_ERROR;
 }
@@ -639,6 +738,7 @@ write_prop(void *baton, const char *buf,
  * Use SCRATCH_POOL for temporary allocations. */
 static svn_error_t *
 init_prop_target(prop_patch_target_t **prop_target,
+                 const patch_target_t *target,
                  const char *prop_name,
                  svn_diff_operation_kind_t operation,
                  svn_wc_context_t *wc_ctx,
@@ -648,7 +748,6 @@ init_prop_target(prop_patch_target_t **p
   prop_patch_target_t *new_prop_target;
   target_content_t *content;
   const svn_string_t *value;
-  svn_error_t *err;
   prop_read_baton_t *prop_read_baton;
 
   content = apr_pcalloc(result_pool, sizeof(*content));
@@ -665,18 +764,12 @@ init_prop_target(prop_patch_target_t **p
   new_prop_target->operation = operation;
   new_prop_target->content = content;
 
-  err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name,
-                         result_pool, scratch_pool);
-  if (err)
-    {
-      if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
-        {
-          svn_error_clear(err);
-          value = NULL;
-        }
-      else
-        return svn_error_trace(err);
-    }
+  if (!(target->deleted || target->db_kind == svn_node_none))
+    SVN_ERR(svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name,
+                             result_pool, scratch_pool));
+  else
+    value = NULL;
+
   content->existed = (value != NULL);
   new_prop_target->value = value;
   new_prop_target->patched_value = svn_stringbuf_create_empty(result_pool);
@@ -717,71 +810,15 @@ readline_file(void *baton, svn_stringbuf
               svn_boolean_t *eof, apr_pool_t *result_pool,
               apr_pool_t *scratch_pool)
 {
-  apr_file_t *file = (apr_file_t *)baton;
-  svn_stringbuf_t *str = NULL;
-  apr_size_t numbytes;
-  char c;
-  svn_boolean_t found_eof;
+  apr_file_t *file = baton;
 
-  /* Read bytes into STR up to and including, but not storing,
-   * the next EOL sequence. */
-  *eol_str = NULL;
-  numbytes = 1;
-  found_eof = FALSE;
-  while (!found_eof)
-    {
-      SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
-                                     &found_eof, scratch_pool));
-      if (numbytes != 1)
-        {
-          found_eof = TRUE;
-          break;
-        }
-
-      if (c == '\n')
-        {
-          *eol_str = "\n";
-        }
-      else if (c == '\r')
-        {
-          *eol_str = "\r";
+  SVN_ERR(svn_io_file_readline(file, line, eol_str, eof, APR_SIZE_MAX,
+                               result_pool, scratch_pool));
 
-          if (!found_eof)
-            {
-              apr_off_t pos;
-
-              /* Check for "\r\n" by peeking at the next byte. */
-              pos = 0;
-              SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool));
-              SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes,
-                                             &found_eof, scratch_pool));
-              if (numbytes == 1 && c == '\n')
-                {
-                  *eol_str = "\r\n";
-                }
-              else
-                {
-                  /* Pretend we never peeked. */
-                  SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool));
-                  found_eof = FALSE;
-                  numbytes = 1;
-                }
-            }
-        }
-      else
-        {
-          if (str == NULL)
-            str = svn_stringbuf_create_ensure(80, result_pool);
-          svn_stringbuf_appendbyte(str, c);
-        }
-
-      if (*eol_str)
-        break;
-    }
-
-  if (eof)
-    *eof = found_eof;
-  *line = str;
+  if (!(*line)->len)
+    *line = NULL;
+  else
+    *eof = FALSE;
 
   return SVN_NO_ERROR;
 }
@@ -792,7 +829,8 @@ readline_file(void *baton, svn_stringbuf
 static svn_error_t *
 tell_file(void *baton, apr_off_t *offset, apr_pool_t *scratch_pool)
 {
-  apr_file_t *file = (apr_file_t *)baton;
+  apr_file_t *file = baton;
+
   *offset = 0;
   SVN_ERR(svn_io_file_seek(file, APR_CUR, offset, scratch_pool));
   return SVN_NO_ERROR;
@@ -803,7 +841,8 @@ tell_file(void *baton, apr_off_t *offset
 static svn_error_t *
 seek_file(void *baton, apr_off_t offset, apr_pool_t *scratch_pool)
 {
-  apr_file_t *file = (apr_file_t *)baton;
+  apr_file_t *file = baton;
+
   SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, scratch_pool));
   return SVN_NO_ERROR;
 }
@@ -814,27 +853,15 @@ static svn_error_t *
 write_file(void *baton, const char *buf, apr_size_t len,
            apr_pool_t *scratch_pool)
 {
-  apr_file_t *file = (apr_file_t *)baton;
+  apr_file_t *file = baton;
+
   SVN_ERR(svn_io_file_write_full(file, buf, len, &len, scratch_pool));
   return SVN_NO_ERROR;
 }
 
-/* Handling symbolic links:
- *
- * In Subversion, symlinks can be represented on disk in two distinct ways.
- * On systems which support symlinks, a symlink is created on disk.
- * On systems which do not support symlink, a file is created on disk
- * which contains the "normal form" of the symlink, which looks like:
- *   link TARGET
- * where TARGET is the file the symlink points to.
- *
- * When reading symlinks (i.e. the link itself, not the file the symlink
- * is pointing to) through the svn_subst_create_specialfile() function
- * into a buffer, the buffer always contains the "normal form" of the symlink.
- * Due to this representation symlinks always contain a single line of text.
- *
- * The functions below are needed to deal with the case where a patch
- * wants to change the TARGET that a symlink points to.
+/* Symlinks appear in patches in their repository normal form, abstracted by
+ * the svn_subst_* module.  The functions below enable patches to change the
+ * targets of symlinks.
  */
 
 /* Baton for the (readline|tell|seek|write)_symlink functions. */
@@ -870,16 +897,36 @@ readline_symlink(void *baton, svn_string
     }
   else
     {
-      svn_string_t *dest;
+      svn_stream_t *stream;
+      const apr_size_t len_hint = 64; /* arbitrary */
 
-      SVN_ERR(svn_io_read_link(&dest, sb->local_abspath, scratch_pool));
-      *line = svn_stringbuf_createf(result_pool, "link %s", dest->data);
+      SVN_ERR(svn_subst_read_specialfile(&stream, sb->local_abspath,
+                                         scratch_pool, scratch_pool));
+      SVN_ERR(svn_stringbuf_from_stream(line, stream, len_hint, result_pool));
+      *eof = FALSE;
       sb->at_eof = TRUE;
     }
 
   return SVN_NO_ERROR;
 }
 
+/* Identical to readline_symlink(), but returns symlink in raw format to
+ * allow patching links in git-style.
+ */
+static svn_error_t *
+readline_symlink_git(void *baton, svn_stringbuf_t **line, const char **eol_str,
+                     svn_boolean_t *eof, apr_pool_t *result_pool,
+                     apr_pool_t *scratch_pool)
+{
+  SVN_ERR(readline_symlink(baton, line, eol_str, eof,
+                           result_pool, scratch_pool));
+
+  if (*line && (*line)->len > 5 && !strncmp((*line)->data, "link ", 5))
+    svn_stringbuf_remove(*line, 0, 5); /* Skip "link " */
+
+  return SVN_NO_ERROR;
+}
+
 /* Set *OFFSET to 1 or 0 depending on whether the "normal form" of
  * the symlink has already been read. */
 static svn_error_t *
@@ -902,35 +949,6 @@ seek_symlink(void *baton, apr_off_t offs
   return SVN_NO_ERROR;
 }
 
-
-/* Set the target of the symlink accessed via BATON.
- * The contents of BUF must be a valid "normal form" of a symlink. */
-static svn_error_t *
-write_symlink(void *baton, const char *buf, apr_size_t len,
-              apr_pool_t *scratch_pool)
-{
-  const char *target_abspath = baton;
-  const char *new_name;
-  const char *link = apr_pstrndup(scratch_pool, buf, len);
-
-  if (strncmp(link, "link ", 5) != 0)
-    return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL,
-                            _("Invalid link representation"));
-
-  link += 5; /* Skip "link " */
-
-  /* We assume the entire symlink is written at once, as the patch
-     format is line based */
-
-  SVN_ERR(svn_io_create_unique_link(&new_name, target_abspath, link,
-                                    ".tmp", scratch_pool));
-
-  SVN_ERR(svn_io_file_rename(new_name, target_abspath, scratch_pool));
-
-  return SVN_NO_ERROR;
-}
-
-
 /* Return a suitable filename for the target of PATCH.
  * Examine the ``old'' and ``new'' file names, and choose the file name
  * with the fewest path components, the shortest basename, and the shortest
@@ -988,32 +1006,19 @@ choose_target_filename(const svn_patch_t
 static svn_error_t *
 init_patch_target(patch_target_t **patch_target,
                   const svn_patch_t *patch,
-                  const char *wcroot_abspath,
+                  const char *root_abspath,
                   svn_wc_context_t *wc_ctx, int strip_count,
                   svn_boolean_t remove_tempfiles,
+                  const apr_array_header_t *targets_info,
                   apr_pool_t *result_pool, apr_pool_t *scratch_pool)
 {
   patch_target_t *target;
   target_content_t *content;
-  svn_boolean_t has_prop_changes = FALSE;
-  svn_boolean_t prop_changes_only = FALSE;
-
-  {
-    apr_hash_index_t *hi;
-
-    for (hi = apr_hash_first(scratch_pool, patch->prop_patches);
-         hi;
-         hi = apr_hash_next(hi))
-      {
-        svn_prop_patch_t *prop_patch = apr_hash_this_val(hi);
-        if (! has_prop_changes)
-          has_prop_changes = prop_patch->hunks->nelts > 0;
-        else
-          break;
-      }
-  }
+  svn_boolean_t has_text_changes = FALSE;
+  svn_boolean_t follow_moves;
 
-  prop_changes_only = has_prop_changes && patch->hunks->nelts == 0;
+  has_text_changes = ((patch->hunks && patch->hunks->nelts > 0)
+                      || patch->binary_patch);
 
   content = apr_pcalloc(result_pool, sizeof(*content));
 
@@ -1031,57 +1036,36 @@ init_patch_target(patch_target_t **patch
   target->kind_on_disk = svn_node_none;
   target->content = content;
   target->prop_targets = apr_hash_make(result_pool);
+  target->operation = patch->operation;
+
+  if (patch->operation == svn_diff_op_added /* Allow replacing */
+      || patch->operation == svn_diff_op_added
+      || patch->operation == svn_diff_op_moved)
+    {
+      follow_moves = FALSE;
+    }
+  else if (patch->operation == svn_diff_op_unchanged
+           && patch->hunks && patch->hunks->nelts == 1)
+    {
+      svn_diff_hunk_t *hunk = APR_ARRAY_IDX(patch->hunks, 0,
+                                            svn_diff_hunk_t *);
+
+      follow_moves = (svn_diff_hunk_get_original_start(hunk) != 0);
+    }
+  else
+    follow_moves = TRUE;
 
   SVN_ERR(resolve_target_path(target, choose_target_filename(patch),
-                              wcroot_abspath, strip_count, prop_changes_only,
-                              wc_ctx, result_pool, scratch_pool));
+                              root_abspath, strip_count, has_text_changes,
+                              follow_moves, wc_ctx, targets_info,
+                              result_pool, scratch_pool));
   *patch_target = target;
   if (! target->skipped)
     {
-      const char *diff_header;
-      apr_size_t len;
-
-      /* Create a temporary file to write the patched result to.
-       * Also grab various bits of information about the file. */
-      if (target->is_symlink)
-        {
-          struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb));
-          content->existed = TRUE;
-
-          sb->local_abspath = target->local_abspath;
-
-          /* Wire up the read callbacks. */
-          content->read_baton = sb;
-
-          content->readline = readline_symlink;
-          content->seek = seek_symlink;
-          content->tell = tell_symlink;
-        }
-      else if (target->kind_on_disk == svn_node_file)
+      if (patch->old_symlink_bit == svn_tristate_true
+          || patch->new_symlink_bit == svn_tristate_true)
         {
-          SVN_ERR(svn_io_file_open(&target->file, target->local_abspath,
-                                   APR_READ | APR_BUFFERED,
-                                   APR_OS_DEFAULT, result_pool));
-          SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx,
-                                          target->local_abspath, FALSE,
-                                          scratch_pool));
-          SVN_ERR(svn_io_is_file_executable(&target->executable,
-                                            target->local_abspath,
-                                            scratch_pool));
-          SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords,
-                                                   &content->eol_style,
-                                                   &content->eol_str,
-                                                   wc_ctx,
-                                                   target->local_abspath,
-                                                   result_pool,
-                                                   scratch_pool));
-          content->existed = TRUE;
-
-          /* Wire up the read callbacks. */
-          content->readline = readline_file;
-          content->seek = seek_file;
-          content->tell = tell_file;
-          content->read_baton = target->file;
+          target->git_symlink_format = TRUE;
         }
 
       /* ### Is it ok to set the operation of the target already here? Isn't
@@ -1099,6 +1083,7 @@ init_patch_target(patch_target_t **patch
           const char *move_target_path;
           const char *move_target_relpath;
           svn_boolean_t under_root;
+          svn_boolean_t is_special;
           svn_node_kind_t kind_on_disk;
           svn_node_kind_t wc_kind;
 
@@ -1111,7 +1096,7 @@ init_patch_target(patch_target_t **patch
 
           if (svn_dirent_is_absolute(move_target_path))
             {
-              move_target_relpath = svn_dirent_is_child(wcroot_abspath,
+              move_target_relpath = svn_dirent_is_child(root_abspath,
                                                         move_target_path,
                                                         scratch_pool);
               if (! move_target_relpath)
@@ -1119,7 +1104,6 @@ init_patch_target(patch_target_t **patch
                   /* The move target path is either outside of the working
                    * copy or it is the working copy itself. Skip it. */
                   target->skipped = TRUE;
-                  target->local_abspath = NULL;
                   return SVN_NO_ERROR;
                 }
             }
@@ -1129,76 +1113,133 @@ init_patch_target(patch_target_t **patch
           /* Make sure the move target path is secure to use. */
           SVN_ERR(svn_dirent_is_under_root(&under_root,
                                            &target->move_target_abspath,
-                                           wcroot_abspath,
+                                           root_abspath,
                                            move_target_relpath, result_pool));
           if (! under_root)
             {
               /* The target path is outside of the working copy. Skip it. */
               target->skipped = TRUE;
-              target->local_abspath = NULL;
+              target->move_target_abspath = NULL;
               return SVN_NO_ERROR;
             }
 
-          SVN_ERR(svn_io_check_path(target->move_target_abspath,
-                                    &kind_on_disk, scratch_pool));
+          SVN_ERR(svn_io_check_special_path(target->move_target_abspath,
+                                            &kind_on_disk, &is_special,
+                                            scratch_pool));
           SVN_ERR(svn_wc_read_kind2(&wc_kind, wc_ctx,
                                     target->move_target_abspath,
                                     FALSE, FALSE, scratch_pool));
-          if (kind_on_disk != svn_node_none || wc_kind != svn_node_none)
+          if (wc_kind == svn_node_file || wc_kind == svn_node_dir)
             {
-              /* The move target path already exists on disk. Skip target. */
-              target->skipped = TRUE;
-              target->move_target_abspath = NULL;
-              return SVN_NO_ERROR;
+              /* The move target path already exists on disk. */
+              svn_error_t *err;
+              const char *moved_from_abspath;
+
+              err = svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
+                                                wc_ctx,
+                                                target->move_target_abspath,
+                                                scratch_pool, scratch_pool);
+
+              if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+                {
+                  svn_error_clear(err);
+                  err = NULL;
+                  moved_from_abspath = NULL;
+                }
+              else
+                SVN_ERR(err);
+
+              if (moved_from_abspath && (strcmp(moved_from_abspath,
+                                                target->local_abspath) == 0))
+                {
+                  target->local_abspath = target->move_target_abspath;
+                  target->move_target_abspath = NULL;
+                  target->operation = svn_diff_op_modified;
+                  target->locally_deleted = FALSE;
+                  target->db_kind = wc_kind;
+                  target->kind_on_disk = kind_on_disk;
+                  target->is_special = is_special;
+
+                  target->had_already_applied = TRUE; /* Make sure we notify */
+                }
+              else
+                {
+                  target->skipped = TRUE;
+                  target->move_target_abspath = NULL;
+                  return SVN_NO_ERROR;
+                }
+
+            }
+          else if (kind_on_disk != svn_node_none
+              || target_is_added(targets_info, target->move_target_abspath,
+                                 scratch_pool))
+            {
+                  target->skipped = TRUE;
+                  target->move_target_abspath = NULL;
+                  return SVN_NO_ERROR;
             }
         }
 
-      if (! target->is_symlink)
+      /* Create a temporary file to write the patched result to.
+       * Also grab various bits of information about the file. */
+      if (target->is_symlink)
         {
-          /* Open a temporary file to write the patched result to. */
-          SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
-                                           &target->patched_path, NULL,
-                                           remove_tempfiles ?
-                                             svn_io_file_del_on_pool_cleanup :
-                                             svn_io_file_del_none,
-                                           result_pool, scratch_pool));
-
-          /* Put the write callback in place. */
-          content->write = write_file;
-          content->write_baton = target->patched_file;
+          struct symlink_baton_t *sb = apr_pcalloc(result_pool, sizeof(*sb));
+          content->existed = TRUE;
+
+          sb->local_abspath = target->local_abspath;
+
+          /* Wire up the read callbacks. */
+          content->read_baton = sb;
+
+          content->readline = target->git_symlink_format ? readline_symlink_git
+                                                         : readline_symlink;
+          content->seek = seek_symlink;
+          content->tell = tell_symlink;
         }
-      else
+      else if (target->kind_on_disk == svn_node_file)
         {
-          /* Put the write callback in place. */
-          SVN_ERR(svn_io_open_unique_file3(NULL,
-                                           &target->patched_path, NULL,
-                                           remove_tempfiles ?
-                                             svn_io_file_del_on_pool_cleanup :
-                                             svn_io_file_del_none,
-                                           result_pool, scratch_pool));
-
-          content->write_baton = (void*)target->patched_path;
-
-          content->write = write_symlink;
-        }
-
-      /* Open a temporary file to write rejected hunks to. */
-      SVN_ERR(svn_io_open_unique_file3(&target->reject_file,
-                                       &target->reject_path, NULL,
-                                       remove_tempfiles ?
+          SVN_ERR(svn_io_file_open(&target->file, target->local_abspath,
+                                   APR_READ | APR_BUFFERED,
+                                   APR_OS_DEFAULT, result_pool));
+          SVN_ERR(svn_io_is_file_executable(&target->executable,
+                                            target->local_abspath,
+                                            scratch_pool));
+          SVN_ERR(obtain_eol_and_keywords_for_file(&content->keywords,
+                                                   &content->eol_style,
+                                                   &content->eol_str,
+                                                   wc_ctx,
+                                                   target->local_abspath,
+                                                   result_pool,
+                                                   scratch_pool));
+          content->existed = TRUE;
+
+          /* Wire up the read callbacks. */
+          content->readline = readline_file;
+          content->seek = seek_file;
+          content->tell = tell_file;
+          content->read_baton = target->file;
+        }
+
+      /* Open a temporary file to write the patched result to. */
+      SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
+                                        &target->patched_path, NULL,
+                                        remove_tempfiles ?
+                                          svn_io_file_del_on_pool_cleanup :
+                                          svn_io_file_del_none,
+                                        result_pool, scratch_pool));
+
+      /* Put the write callback in place. */
+      content->write = write_file;
+      content->write_baton = target->patched_file;
+
+      /* Open a temporary stream to write rejected hunks to. */
+      SVN_ERR(svn_stream_open_unique(&target->reject_stream,
+                                     &target->reject_path, NULL,
+                                     remove_tempfiles ?
                                          svn_io_file_del_on_pool_cleanup :
                                          svn_io_file_del_none,
-                                       result_pool, scratch_pool));
-
-      /* The reject file needs a diff header. */
-      diff_header = apr_psprintf(scratch_pool, "--- %s%s+++ %s%s",
-                                 target->canon_path_from_patchfile,
-                                 APR_EOL_STR,
-                                 target->canon_path_from_patchfile,
-                                 APR_EOL_STR);
-      len = strlen(diff_header);
-      SVN_ERR(svn_io_file_write_full(target->reject_file, diff_header, len,
-                                     &len, scratch_pool));
+                                     result_pool, scratch_pool));
 
       /* Handle properties. */
       if (! target->skipped)
@@ -1214,13 +1255,185 @@ init_patch_target(patch_target_t **patch
               prop_patch_target_t *prop_target;
 
               SVN_ERR(init_prop_target(&prop_target,
-                                       prop_name,
+                                       target, prop_name,
                                        prop_patch->operation,
                                        wc_ctx, target->local_abspath,
                                        result_pool, scratch_pool));
               svn_hash_sets(target->prop_targets, prop_name, prop_target);
             }
+
+          /* Now, check for out-of-band mode changes and convert these in
+             their Subversion equivalent properties. */
+          if (patch->new_executable_bit != svn_tristate_unknown
+              && patch->new_executable_bit != patch->old_executable_bit)
+            {
+              svn_diff_operation_kind_t operation;
+
+              if (patch->new_executable_bit == svn_tristate_true)
+                operation = svn_diff_op_added;
+              else if (patch->new_executable_bit == svn_tristate_false)
+                {
+                  /* Made non-executable. */
+                  if (patch->old_executable_bit == svn_tristate_true)
+                    operation = svn_diff_op_deleted;
+                  else
+                    operation = svn_diff_op_unchanged;
+                }
+              else
+                operation = svn_diff_op_unchanged;
+
+              if (operation != svn_diff_op_unchanged)
+                {
+                  prop_patch_target_t *prop_target;
+
+                  prop_target = svn_hash_gets(target->prop_targets,
+                                              SVN_PROP_EXECUTABLE);
+
+                  if (prop_target && operation != prop_target->operation)
+                    {
+                      return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL,
+                                               _("Invalid patch: specifies "
+                                                 "contradicting mode changes and "
+                                                 "%s changes (for '%s')"),
+                                               SVN_PROP_EXECUTABLE,
+                                               target->local_abspath);
+                    }
+                  else if (!prop_target)
+                    {
+                      SVN_ERR(init_prop_target(&prop_target,
+                                               target, SVN_PROP_EXECUTABLE,
+                                               operation,
+                                               wc_ctx, target->local_abspath,
+                                               result_pool, scratch_pool));
+                      svn_hash_sets(target->prop_targets, SVN_PROP_EXECUTABLE,
+                                    prop_target);
+                    }
+                }
+            }
+
+          if (patch->new_symlink_bit != svn_tristate_unknown
+              && patch->new_symlink_bit != patch->old_symlink_bit)
+            {
+              svn_diff_operation_kind_t operation;
+
+              if (patch->new_symlink_bit == svn_tristate_true)
+                operation = svn_diff_op_added;
+              else if (patch->new_symlink_bit == svn_tristate_false)
+                {
+                  /* Made non-symlink. */
+                  if (patch->old_symlink_bit == svn_tristate_true)
+                    operation = svn_diff_op_deleted;
+                  else
+                    operation = svn_diff_op_unchanged;
+                }
+              else
+                operation = svn_diff_op_unchanged;
+
+              if (operation != svn_diff_op_unchanged)
+                {
+                  prop_patch_target_t *prop_target;
+                  prop_target = svn_hash_gets(target->prop_targets,
+                                              SVN_PROP_SPECIAL);
+
+                  if (prop_target && operation != prop_target->operation)
+                    {
+                      return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL,
+                                               _("Invalid patch: specifies "
+                                                 "contradicting mode changes and "
+                                                 "%s changes (for '%s')"),
+                                               SVN_PROP_SPECIAL,
+                                               target->local_abspath);
+                    }
+                  else if (!prop_target)
+                    {
+                      SVN_ERR(init_prop_target(&prop_target,
+                                               target, SVN_PROP_SPECIAL,
+                                               operation,
+                                               wc_ctx, target->local_abspath,
+                                               result_pool, scratch_pool));
+                      svn_hash_sets(target->prop_targets, SVN_PROP_SPECIAL,
+                                    prop_target);
+                    }
+                }
+            }
+        }
+    }
+
+  if ((target->locally_deleted || target->db_kind == svn_node_none)
+      && !target->added
+      && target->operation == svn_diff_op_unchanged)
+    {
+      svn_boolean_t maybe_add = FALSE;
+
+      if (patch->hunks && patch->hunks->nelts == 1)
+        {
+          svn_diff_hunk_t *hunk = APR_ARRAY_IDX(patch->hunks, 0,
+                                                svn_diff_hunk_t *);
+
+          if (svn_diff_hunk_get_original_start(hunk) == 0)
+            maybe_add = TRUE;
+        }
+      else if (patch->prop_patches && apr_hash_count(patch->prop_patches))
+        {
+          apr_hash_index_t *hi;
+          svn_boolean_t all_add = TRUE;
+
+          for (hi = apr_hash_first(result_pool, patch->prop_patches);
+               hi;
+               hi = apr_hash_next(hi))
+            {
+              svn_prop_patch_t *prop_patch = apr_hash_this_val(hi);
+
+              if (prop_patch->operation != svn_diff_op_added)
+                {
+                  all_add = FALSE;
+                  break;
+                }
+            }
+
+          maybe_add = all_add;
         }
+      /* Other implied types */
+
+      if (maybe_add)
+        target->added = TRUE;
+    }
+  else if (!target->deleted && !target->added
+           && target->operation == svn_diff_op_unchanged)
+    {
+      svn_boolean_t maybe_delete = FALSE;
+
+      if (patch->hunks && patch->hunks->nelts == 1)
+        {
+          svn_diff_hunk_t *hunk = APR_ARRAY_IDX(patch->hunks, 0,
+                                                svn_diff_hunk_t *);
+
+          if (svn_diff_hunk_get_modified_start(hunk) == 0)
+            maybe_delete = TRUE;
+        }
+
+      /* Other implied types */
+
+      if (maybe_delete)
+        target->deleted = TRUE;
+    }
+
+  if (target->reject_stream != NULL)
+    {
+      /* The reject file needs a diff header. */
+      const char *left_src = target->canon_path_from_patchfile;
+      const char *right_src = target->canon_path_from_patchfile;
+
+      /* Handle moves specifically? */
+      if (target->added)
+        left_src = "/dev/null";
+      if (target->deleted)
+        right_src = "/dev/null";
+
+      SVN_ERR(svn_stream_printf(target->reject_stream, scratch_pool,
+                                "--- %s" APR_EOL_STR
+                                "+++ %s" APR_EOL_STR,
+                                left_src, right_src));
     }
 
   return SVN_NO_ERROR;
@@ -1569,14 +1782,14 @@ match_existing_target(svn_boolean_t *mat
           *match = FALSE;
           return SVN_NO_ERROR;
         }
-      }
-    while (lines_matched && ! content->eof && ! hunk_eof);
-    svn_pool_destroy(iterpool);
+    }
+  while (lines_matched && ! content->eof && ! hunk_eof);
+  svn_pool_destroy(iterpool);
 
-    *match = (lines_matched && content->eof == hunk_eof);
-    SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
+  *match = (lines_matched && content->eof == hunk_eof);
+  SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
 
-    return SVN_NO_ERROR;
+  return SVN_NO_ERROR;
 }
 
 /* Determine the line at which a HUNK applies to CONTENT of the TARGET
@@ -1637,8 +1850,6 @@ get_hunk_info(hunk_info_t **hi, patch_ta
                 {
                   svn_boolean_t file_matches;
 
-                  /* ### I can't reproduce anything but a no-match here.
-                         The content is already at eof, so any hunk fails */
                   SVN_ERR(match_existing_target(&file_matches, content, hunk,
                                             scratch_pool));
                   if (file_matches)
@@ -1683,8 +1894,11 @@ get_hunk_info(hunk_info_t **hi, patch_ta
     }
   else if (original_start > 0 && content->existed)
     {
+      svn_linenum_t modified_start;
       svn_linenum_t saved_line = content->current_line;
 
+      modified_start = svn_diff_hunk_get_modified_start(hunk);
+
       /* Scan for a match at the line where the hunk thinks it
        * should be going. */
       SVN_ERR(seek_to_line(content, original_start, scratch_pool));
@@ -1708,20 +1922,24 @@ get_hunk_info(hunk_info_t **hi, patch_ta
            * check would be ambiguous. */
           if (fuzz == 0)
             {
-              svn_linenum_t modified_start;
-
-              modified_start = svn_diff_hunk_get_modified_start(hunk);
-              if (modified_start == 0)
+              if (modified_start == 0
+                  && (target->operation == svn_diff_op_unchanged
+                      || target->operation == svn_diff_op_deleted))
                 {
-                  /* Patch wants to delete the file.
+                  /* Patch wants to delete the file. */
 
-                     ### locally_deleted is always false here? */
                   already_applied = target->locally_deleted;
                 }
               else
                 {
-                  SVN_ERR(seek_to_line(content, modified_start,
-                                       scratch_pool));
+                  svn_linenum_t seek_to;
+
+                  if (modified_start == 0)
+                    seek_to = 1; /* Empty file case */
+                  else
+                    seek_to = modified_start;
+
+                  SVN_ERR(seek_to_line(content, seek_to, scratch_pool));
                   SVN_ERR(scan_for_match(&matched_line, content,
                                          hunk, TRUE,
                                          modified_start + 1,
@@ -1818,12 +2036,42 @@ get_hunk_info(hunk_info_t **hi, patch_ta
                 }
             }
         }
+      else if (matched_line > 0
+               && fuzz == 0
+               && (svn_diff_hunk_get_leading_context(hunk) == 0
+                   || svn_diff_hunk_get_trailing_context(hunk) == 0)
+               && (svn_diff_hunk_get_modified_length(hunk) >
+                                    svn_diff_hunk_get_original_length(hunk)))
+        {
+          /* Check that we are not applying the same change that just adds some
+             lines again, when we don't have enough context to see the
+             difference */
+          svn_linenum_t reverse_matched_line;
+
+          SVN_ERR(seek_to_line(content, modified_start, scratch_pool));
+          SVN_ERR(scan_for_match(&reverse_matched_line, content,
+                                  hunk, TRUE,
+                                  modified_start + 1,
+                                  fuzz, ignore_whitespace, TRUE,
+                                  cancel_func, cancel_baton,
+                                  scratch_pool));
+
+          /* We might want to check that we are actually at the start or the
+             end of the file. Having no context implies that we should be. */
+          already_applied = (reverse_matched_line == modified_start);
+        }
 
       SVN_ERR(seek_to_line(content, saved_line, scratch_pool));
     }
+  else if (!content->existed && svn_diff_hunk_get_modified_start(hunk) == 0)
+    {
+      /* The hunk wants to delete a file or property which doesn't exist. */
+      matched_line = 0;
+      already_applied = TRUE;
+    }
   else
     {
-      /* The hunk wants to modify a file which doesn't exist. */
+      /* The hunk wants to modify a file or property which doesn't exist. */
       matched_line = 0;
     }
 
@@ -1878,8 +2126,6 @@ reject_hunk(patch_target_t *target, targ
             svn_diff_hunk_t *hunk, const char *prop_name,
             apr_pool_t *pool)
 {
-  const char *hunk_header;
-  apr_size_t len;
   svn_boolean_t eof;
   static const char * const text_atat = "@@";
   static const char * const prop_atat = "##";
@@ -1888,14 +2134,9 @@ reject_hunk(patch_target_t *target, targ
 
   if (prop_name)
     {
-      const char *prop_header;
-
-      /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'.
-       */
-      prop_header = apr_psprintf(pool, "Property: %s\n", prop_name);
-      len = strlen(prop_header);
-      SVN_ERR(svn_io_file_write_full(target->reject_file, prop_header,
-                                     len, &len, pool));
+      /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'. */
+      svn_stream_printf(target->reject_stream,
+                        pool, "Property: %s" APR_EOL_STR, prop_name);
       atat = prop_atat;
     }
   else
@@ -1903,17 +2144,14 @@ reject_hunk(patch_target_t *target, targ
       atat = text_atat;
     }
 
-  hunk_header = apr_psprintf(pool, "%s -%lu,%lu +%lu,%lu %s%s",
-                             atat,
-                             svn_diff_hunk_get_original_start(hunk),
-                             svn_diff_hunk_get_original_length(hunk),
-                             svn_diff_hunk_get_modified_start(hunk),
-                             svn_diff_hunk_get_modified_length(hunk),
-                             atat,
-                             APR_EOL_STR);
-  len = strlen(hunk_header);
-  SVN_ERR(svn_io_file_write_full(target->reject_file, hunk_header, len,
-                                 &len, pool));
+  SVN_ERR(svn_stream_printf(target->reject_stream, pool,
+                            "%s -%lu,%lu +%lu,%lu %s" APR_EOL_STR,
+                            atat,
+                            svn_diff_hunk_get_original_start(hunk),
+                            svn_diff_hunk_get_original_length(hunk),
+                            svn_diff_hunk_get_modified_start(hunk),
+                            svn_diff_hunk_get_modified_length(hunk),
+                            atat));
 
   iterpool = svn_pool_create(pool);
   do
@@ -1929,17 +2167,15 @@ reject_hunk(patch_target_t *target, targ
         {
           if (hunk_line->len >= 1)
             {
-              len = hunk_line->len;
-              SVN_ERR(svn_io_file_write_full(target->reject_file,
-                                             hunk_line->data, len, &len,
-                                             iterpool));
+              apr_size_t len = hunk_line->len;
+
+              SVN_ERR(svn_stream_write(target->reject_stream,
+                                       hunk_line->data, &len));
             }
 
           if (eol_str)
             {
-              len = strlen(eol_str);
-              SVN_ERR(svn_io_file_write_full(target->reject_file, eol_str,
-                                             len, &len, iterpool));
+              SVN_ERR(svn_stream_puts(target->reject_stream, eol_str));
             }
         }
     }
@@ -2093,7 +2329,7 @@ send_hunk_notification(const hunk_info_t
 static svn_error_t *
 send_patch_notification(const patch_target_t *target,
                         const svn_client_ctx_t *ctx,
-                        apr_pool_t *pool)
+                        apr_pool_t *scratch_pool)
 {
   svn_wc_notify_t *notify;
   svn_wc_notify_action_t action;
@@ -2106,7 +2342,7 @@ send_patch_notification(const patch_targ
     action = svn_wc_notify_skip;
   else if (target->deleted)
     action = svn_wc_notify_delete;
-  else if (target->added || target->replaced || target->move_target_abspath)
+  else if (target->added || target->move_target_abspath)
     action = svn_wc_notify_add;
   else
     action = svn_wc_notify_patch;
@@ -2117,16 +2353,17 @@ send_patch_notification(const patch_targ
     notify_path = target->local_abspath ? target->local_abspath
                                         : target->local_relpath;
 
-  notify = svn_wc_create_notify(notify_path, action, pool);
-  notify->kind = svn_node_file;
+  notify = svn_wc_create_notify(notify_path, action, scratch_pool);
+  notify->kind = (target->db_kind == svn_node_dir) ? svn_node_dir
+                                                   : svn_node_file;
 
   if (action == svn_wc_notify_skip)
     {
-      if (target->db_kind == svn_node_none ||
-          target->db_kind == svn_node_unknown)
-        notify->content_state = svn_wc_notify_state_missing;
-      else if (target->db_kind == svn_node_dir)
+      if (target->obstructed)
         notify->content_state = svn_wc_notify_state_obstructed;
+      else if (target->db_kind == svn_node_none ||
+               target->db_kind == svn_node_unknown)
+        notify->content_state = svn_wc_notify_state_missing;
       else
         notify->content_state = svn_wc_notify_state_unknown;
     }
@@ -2134,26 +2371,32 @@ send_patch_notification(const patch_targ
     {
       if (target->had_rejects)
         notify->content_state = svn_wc_notify_state_conflicted;
-      else if (target->local_mods)
-        notify->content_state = svn_wc_notify_state_merged;
       else if (target->has_text_changes)
         notify->content_state = svn_wc_notify_state_changed;
+      else if (target->had_already_applied)
+        notify->content_state = svn_wc_notify_state_merged;
+      else
+        notify->content_state = svn_wc_notify_state_unchanged;
 
       if (target->had_prop_rejects)
         notify->prop_state = svn_wc_notify_state_conflicted;
       else if (target->has_prop_changes)
         notify->prop_state = svn_wc_notify_state_changed;
+      else if (target->had_prop_already_applied)
+        notify->prop_state = svn_wc_notify_state_merged;
+      else
+        notify->prop_state = svn_wc_notify_state_unchanged;
     }
 
-  ctx->notify_func2(ctx->notify_baton2, notify, pool);
+  ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
 
   if (action == svn_wc_notify_patch)
     {
       int i;
       apr_pool_t *iterpool;
-      apr_hash_index_t *hash_index;
+      apr_array_header_t *prop_targets;
 
-      iterpool = svn_pool_create(pool);
+      iterpool = svn_pool_create(scratch_pool);
       for (i = 0; i < target->content->hunks->nelts; i++)
         {
           const hunk_info_t *hi;
@@ -2166,26 +2409,30 @@ send_patch_notification(const patch_targ
                                          ctx, iterpool));
         }
 
-      for (hash_index = apr_hash_first(pool, target->prop_targets);
-           hash_index;
-           hash_index = apr_hash_next(hash_index))
-        {
-          prop_patch_target_t *prop_target;
+      prop_targets = svn_sort__hash(target->prop_targets,
+                                    svn_sort_compare_items_lexically,
+                                    scratch_pool);
+      for (i = 0; i < prop_targets->nelts; i++)
+        {
+          int j;
+          svn_sort__item_t item = APR_ARRAY_IDX(prop_targets, i,
+                                                svn_sort__item_t);
 
-          prop_target = apr_hash_this_val(hash_index);
+          prop_patch_target_t *prop_target = item.value;
 
-          for (i = 0; i < prop_target->content->hunks->nelts; i++)
+          for (j = 0; j < prop_target->content->hunks->nelts; j++)
             {
               const hunk_info_t *hi;
 
               svn_pool_clear(iterpool);
 
-              hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
+              hi = APR_ARRAY_IDX(prop_target->content->hunks, j,
                                  hunk_info_t *);
 
               /* Don't notify on the hunk level for added or deleted props. */
-              if (prop_target->operation != svn_diff_op_added &&
+              if ((prop_target->operation != svn_diff_op_added &&
                   prop_target->operation != svn_diff_op_deleted)
+                  || hi->rejected || hi->already_applied)
                 SVN_ERR(send_hunk_notification(hi, target, prop_target->name,
                                                ctx, iterpool));
             }
@@ -2193,13 +2440,13 @@ send_patch_notification(const patch_targ
       svn_pool_destroy(iterpool);
     }
 
-  if (target->move_target_abspath)
+  if (!target->skipped && target->move_target_abspath)
     {
       /* Notify about deletion of move source. */
       notify = svn_wc_create_notify(target->local_abspath,
-                                    svn_wc_notify_delete, pool);
+                                    svn_wc_notify_delete, scratch_pool);
       notify->kind = svn_node_file;
-      ctx->notify_func2(ctx->notify_baton2, notify, pool);
+      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
     }
 
   return SVN_NO_ERROR;
@@ -2264,8 +2511,7 @@ apply_one_patch(patch_target_t **patch_t
                 int strip_count,
                 svn_boolean_t ignore_whitespace,
                 svn_boolean_t remove_tempfiles,
-                svn_client_patch_func_t patch_func,
-                void *patch_baton,
+                const apr_array_header_t *targets_info,
                 svn_cancel_func_t cancel_func,
                 void *cancel_baton,
                 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
@@ -2276,102 +2522,208 @@ apply_one_patch(patch_target_t **patch_t
   static const svn_linenum_t MAX_FUZZ = 2;
   apr_hash_index_t *hash_index;
   svn_linenum_t previous_offset = 0;
+  apr_array_header_t *prop_targets;
 
   SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count,
-                            remove_tempfiles, result_pool, scratch_pool));
+                            remove_tempfiles, targets_info,
+                            result_pool, scratch_pool));
   if (target->skipped)
     {
       *patch_target = target;
       return SVN_NO_ERROR;
     }
 
-  if (patch_func)
+  iterpool = svn_pool_create(scratch_pool);
+
+  if (patch->hunks && patch->hunks->nelts)
     {
-      SVN_ERR(patch_func(patch_baton, &target->filtered,
-                         target->canon_path_from_patchfile,
-                         target->patched_path, target->reject_path,
-                         scratch_pool));
-      if (target->filtered)
+      /* Match hunks. */
+      for (i = 0; i < patch->hunks->nelts; i++)
         {
-          *patch_target = target;
-          return SVN_NO_ERROR;
-        }
-    }
+          svn_diff_hunk_t *hunk;
+          hunk_info_t *hi;
+          svn_linenum_t fuzz = 0;
 
-  iterpool = svn_pool_create(scratch_pool);
-  /* Match hunks. */
-  for (i = 0; i < patch->hunks->nelts; i++)
-    {
-      svn_diff_hunk_t *hunk;
-      hunk_info_t *hi;
-      svn_linenum_t fuzz = 0;
+          svn_pool_clear(iterpool);
 
-      svn_pool_clear(iterpool);
+          if (cancel_func)
+            SVN_ERR(cancel_func(cancel_baton));
 
-      if (cancel_func)
-        SVN_ERR(cancel_func(cancel_baton));
+          hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *);
+
+          /* Determine the line the hunk should be applied at.
+           * If no match is found initially, try with fuzz. */
+          do
+            {
+              SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz,
+                                    previous_offset,
+                                    ignore_whitespace,
+                                    FALSE /* is_prop_hunk */,
+                                    cancel_func, cancel_baton,
+                                    result_pool, iterpool));
+              fuzz++;
+            }
+          while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
 
-      hunk = APR_ARRAY_IDX(patch->hunks, i, svn_diff_hunk_t *);
+          if (hi->matched_line)
+            previous_offset
+              = hi->matched_line - svn_diff_hunk_get_original_start(hunk);
 
-      /* Determine the line the hunk should be applied at.
-       * If no match is found initially, try with fuzz. */
-      do
-        {
-          SVN_ERR(get_hunk_info(&hi, target, target->content, hunk, fuzz,
-                                previous_offset,
-                                ignore_whitespace,
-                                FALSE /* is_prop_hunk */,
-                                cancel_func, cancel_baton,
-                                result_pool, iterpool));
-          fuzz++;
+          APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi;
         }
-      while (hi->rejected && fuzz <= MAX_FUZZ && ! hi->already_applied);
 
-      if (hi->matched_line)
-        previous_offset
-          = hi->matched_line - svn_diff_hunk_get_original_start(hunk);
+      /* Hunks are applied in the order determined by the matched line and
+         this may be different from the order of the original lines. */
+      svn_sort__array(target->content->hunks, sort_matched_hunks);
 
-      APR_ARRAY_PUSH(target->content->hunks, hunk_info_t *) = hi;
-    }
+      /* Apply or reject hunks. */
+      for (i = 0; i < target->content->hunks->nelts; i++)
+        {
+          hunk_info_t *hi;
+
+          svn_pool_clear(iterpool);
+
+          if (cancel_func)
+            SVN_ERR(cancel_func(cancel_baton));
 
-  /* Hunks are applied in the order determined by the matched line and
-     this may be different from the order of the original lines. */
-  svn_sort__array(target->content->hunks, sort_matched_hunks);
+          hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
+          if (hi->already_applied)
+            {
+              target->had_already_applied = TRUE;
+              continue;
+            }
+          else if (hi->rejected)
+            SVN_ERR(reject_hunk(target, target->content, hi->hunk,
+                                NULL /* prop_name */,
+                                iterpool));
+          else
+            SVN_ERR(apply_hunk(target, target->content, hi,
+                               NULL /* prop_name */,  iterpool));
+        }
 
-  /* Apply or reject hunks. */
-  for (i = 0; i < target->content->hunks->nelts; i++)
+      if (target->kind_on_disk == svn_node_file)
+        {
+          /* Copy any remaining lines to target. */
+          SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
+          if (! target->content->eof)
+            {
+              /* We could not copy the entire target file to the temporary
+               * file, and would truncate the target if we copied the
+               * temporary file on top of it. Skip this target. */
+              target->skipped = TRUE;
+            }
+        }
+    }
+  else if (patch->binary_patch)
     {
-      hunk_info_t *hi;
+      svn_stream_t *orig_stream;
+      svn_boolean_t same;
+
+      if (target->file)
+        orig_stream = svn_stream_from_aprfile2(target->file, TRUE, iterpool);
+      else
+        orig_stream = svn_stream_empty(iterpool);
 
+      SVN_ERR(svn_stream_contents_same2(
+                &same, orig_stream,
+                svn_diff_get_binary_diff_original_stream(patch->binary_patch,
+                                                         iterpool),
+                iterpool));
       svn_pool_clear(iterpool);
 
-      if (cancel_func)
-        SVN_ERR(cancel_func(cancel_baton));
+      if (same)
+        {
+          /* The file in the working copy is identical to the one expected by
+             the patch... So we can write the result stream; no fuzz,
+             just a 100% match */
 
-      hi = APR_ARRAY_IDX(target->content->hunks, i, hunk_info_t *);
-      if (hi->already_applied)
-        continue;
-      else if (hi->rejected)
-        SVN_ERR(reject_hunk(target, target->content, hi->hunk,
-                            NULL /* prop_name */,
-                            iterpool));
+          target->has_text_changes = TRUE;
+        }
       else
-        SVN_ERR(apply_hunk(target, target->content, hi,
-                           NULL /* prop_name */,  iterpool));
-    }
+        {
+          /* Perhaps the file is identical to the resulting version, implying
+             that the patch has already been applied */
+          if (target->file)
+            {
+              apr_off_t start = 0;
 
-  if (target->kind_on_disk == svn_node_file)
-    {
-      /* Copy any remaining lines to target. */
-      SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
-      if (! target->content->eof)
+              SVN_ERR(svn_io_file_seek(target->file, APR_SET, &start, iterpool));
+
+              orig_stream = svn_stream_from_aprfile2(target->file, TRUE, iterpool);
+            }
+          else
+            orig_stream = svn_stream_empty(iterpool);
+
+          SVN_ERR(svn_stream_contents_same2(
+                    &same, orig_stream,
+                    svn_diff_get_binary_diff_result_stream(patch->binary_patch,
+                                                           iterpool),
+                    iterpool));
+          svn_pool_clear(iterpool);
+
+          if (same)
+            target->had_already_applied = TRUE;
+        }
+
+      if (same)
         {
-          /* We could not copy the entire target file to the temporary file,
-           * and would truncate the target if we copied the temporary file
-           * on top of it. Skip this target. */
+          SVN_ERR(svn_stream_copy3(
+                svn_diff_get_binary_diff_result_stream(patch->binary_patch,
+                                                       iterpool),
+                svn_stream_from_aprfile2(target->patched_file, TRUE,
+                                         iterpool),
+                cancel_func, cancel_baton,
+                iterpool));
+        }
+      else
+        {
+          /* ### TODO: Implement a proper reject of a binary patch
+
+             This should at least setup things for a proper notification,
+             and perhaps install a normal text conflict. Unlike normal unified
+             diff based patches we have all the versions we would need for
+             that in a much easier format than can be obtained from the patch
+             file. */
           target->skipped = TRUE;
         }
     }
+  else if (target->move_target_abspath)
+    {
+      /* ### Why do we do this?
+             BH: I don't know, but if we don't do this some tests
+                 on git style patches break.
+
+         ### It would be much better to really move the actual file instead
+             of copying to a temporary file; move that to target and then
+             delete the original file
+
+         ### BH: I have absolutely no idea if moving directories would work.
+       */
+      if (target->kind_on_disk == svn_node_file)
+        {
+          /* Copy any remaining lines to target. (read: all lines) */
+          SVN_ERR(copy_lines_to_target(target->content, 0, scratch_pool));
+          if (!target->content->eof)
+            {
+              /* We could not copy the entire target file to the temporary
+               * file, and would truncate the target if we copied the
+               * temporary file on top of it. Skip this target. */
+              target->skipped = TRUE;
+            }
+        }
+    }
+
+  if (target->had_rejects || target->locally_deleted)
+    target->deleted = FALSE;
+
+  if (target->added
+      && !(target->locally_deleted || target->db_kind == svn_node_none))
+    {
+      target->added = FALSE;
+    }
+
+  /* Assume nothing changed. Will be updated via property hunks */
+  target->is_special = target->is_symlink;
 
   /* Match property hunks. */
   for (hash_index = apr_hash_first(scratch_pool, patch->prop_patches);
@@ -2385,8 +2737,8 @@ apply_one_patch(patch_target_t **patch_t
       prop_name = apr_hash_this_key(hash_index);
       prop_patch = apr_hash_this_val(hash_index);
 
-      if (! strcmp(prop_name, SVN_PROP_SPECIAL))
-        target->is_special = TRUE;
+      if (!strcmp(prop_name, SVN_PROP_SPECIAL))
+        target->is_special = (prop_patch->operation != svn_diff_op_deleted);
 
       /* We'll store matched hunks in prop_content. */
       prop_target = svn_hash_gets(target->prop_targets, prop_name);
@@ -2422,48 +2774,174 @@ apply_one_patch(patch_target_t **patch_t
         }
     }
 
-  /* Apply or reject property hunks. */
-  for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
-       hash_index;
-       hash_index = apr_hash_next(hash_index))
+  /* Match implied property hunks. */
+  if (patch->new_executable_bit != svn_tristate_unknown
+      && patch->new_executable_bit != patch->old_executable_bit
+      && svn_hash_gets(target->prop_targets, SVN_PROP_EXECUTABLE)
+      && !svn_hash_gets(patch->prop_patches, SVN_PROP_EXECUTABLE))
     {
-      prop_patch_target_t *prop_target;
+      hunk_info_t *hi;
+      svn_diff_hunk_t *hunk;
+      prop_patch_target_t *prop_target = svn_hash_gets(target->prop_targets,
+                                                       SVN_PROP_EXECUTABLE);
+
+      if (patch->new_executable_bit == svn_tristate_true)
+        SVN_ERR(svn_diff_hunk__create_adds_single_line(
+                                            &hunk,
+                                            SVN_PROP_EXECUTABLE_VALUE,
+                                            patch,
+                                            result_pool,
+                                            iterpool));
+      else
+        SVN_ERR(svn_diff_hunk__create_deletes_single_line(
+                                            &hunk,
+                                            SVN_PROP_EXECUTABLE_VALUE,
+                                            patch,
+                                            result_pool,
+                                            iterpool));
+
+      /* Derive a hunk_info from hunk. */
+      SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
+                            hunk, 0 /* fuzz */, 0 /* previous_offset */,
+                            ignore_whitespace,
+                            TRUE /* is_prop_hunk */,
+                            cancel_func, cancel_baton,
+                            result_pool, iterpool));
+      APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
+    }
+
+  if (patch->new_symlink_bit != svn_tristate_unknown
+      && patch->new_symlink_bit != patch->old_symlink_bit
+      && svn_hash_gets(target->prop_targets, SVN_PROP_SPECIAL)
+      && !svn_hash_gets(patch->prop_patches, SVN_PROP_SPECIAL))
+    {
+      hunk_info_t *hi;
+      svn_diff_hunk_t *hunk;
+
+      prop_patch_target_t *prop_target = svn_hash_gets(target->prop_targets,
+                                                       SVN_PROP_SPECIAL);
+
+      if (patch->new_symlink_bit == svn_tristate_true)
+        {
+          SVN_ERR(svn_diff_hunk__create_adds_single_line(
+                                            &hunk,
+                                            SVN_PROP_SPECIAL_VALUE,
+                                            patch,
+                                            result_pool,
+                                            iterpool));
+          target->is_special = TRUE;
+        }
+      else
+        {
+          SVN_ERR(svn_diff_hunk__create_deletes_single_line(
+                                            &hunk,
+                                            SVN_PROP_SPECIAL_VALUE,
+                                            patch,
+                                            result_pool,
+                                            iterpool));
+          target->is_special = FALSE;
+        }
+
+      /* Derive a hunk_info from hunk. */
+      SVN_ERR(get_hunk_info(&hi, target, prop_target->content,
+                            hunk, 0 /* fuzz */, 0 /* previous_offset */,
+                            ignore_whitespace,
+                            TRUE /* is_prop_hunk */,
+                            cancel_func, cancel_baton,
+                            result_pool, iterpool));
+      APR_ARRAY_PUSH(prop_target->content->hunks, hunk_info_t *) = hi;
+    }
+
+  /* When the node is deleted or does not exist after the patch is applied
+     we should reject a few more property hunks that can't be applied even
+     though the source matched */
+  if (target->deleted
+      || (!target->added &&
+          (target->locally_deleted || target->db_kind == svn_node_none)))
+    {
+      for (hash_index = apr_hash_first(scratch_pool, target->prop_targets);
+           hash_index;
+           hash_index = apr_hash_next(hash_index))
+        {
+          prop_patch_target_t *prop_target = apr_hash_this_val(hash_index);
+
+          if (prop_target->operation == svn_diff_op_deleted)
+            continue;
+
+          for (i = 0; i < prop_target->content->hunks->nelts; i++)
+            {
+              hunk_info_t *hi;
+
+              hi = APR_ARRAY_IDX(prop_target->content->hunks, i, hunk_info_t*);
+
+              if (hi->already_applied || hi->rejected)
+                continue;
+              else
+                {
+                  hi->rejected = TRUE;
+                  prop_target->skipped = TRUE;
+
+                  if (!target->deleted && !target->added)
+                    target->skipped = TRUE;
+                }
+            }
+        }
+    }
 
-      prop_target = apr_hash_this_val(hash_index);
+  /* Apply or reject property hunks. */
+
+  prop_targets = svn_sort__hash(target->prop_targets,
+                                svn_sort_compare_items_lexically,
+                                scratch_pool);
+  for (i = 0; i < prop_targets->nelts; i++)
+    {
+      svn_sort__item_t item = APR_ARRAY_IDX(prop_targets, i, svn_sort__item_t);
+      prop_patch_target_t *prop_target = item.value;
+      svn_boolean_t applied_one = FALSE;
+      int j;
 
-      for (i = 0; i < prop_target->content->hunks->nelts; i++)
+      for (j = 0; j < prop_target->content->hunks->nelts; j++)
         {
           hunk_info_t *hi;
 
           svn_pool_clear(iterpool);
 
-          hi = APR_ARRAY_IDX(prop_target->content->hunks, i,
+          hi = APR_ARRAY_IDX(prop_target->content->hunks, j,
                              hunk_info_t *);
           if (hi->already_applied)
-            continue;
+            {
+              target->had_prop_already_applied = TRUE;
+              continue;
+            }
           else if (hi->rejected)
             SVN_ERR(reject_hunk(target, prop_target->content, hi->hunk,
                                 prop_target->name,
                                 iterpool));
           else
-            SVN_ERR(apply_hunk(target, prop_target->content, hi,
-                               prop_target->name,
-                               iterpool));
+            {
+              SVN_ERR(apply_hunk(target, prop_target->content, hi,
+                                 prop_target->name,
+                                 iterpool));
+              applied_one = TRUE;
+            }
         }
 
-        if (prop_target->content->existed)
-          {
-            /* Copy any remaining lines to target. */
-            SVN_ERR(copy_lines_to_target(prop_target->content, 0,
-                                         scratch_pool));
-            if (! prop_target->content->eof)
-              {
-                /* We could not copy the entire target property to the
-                 * temporary file, and would truncate the target if we
-                 * copied the temporary file on top of it. Skip this target.  */
-                target->skipped = TRUE;
-              }
-          }
+      if (!applied_one)
+        prop_target->skipped = TRUE;
+
+      if (applied_one && prop_target->content->existed)
+        {
+          /* Copy any remaining lines to target. */
+          SVN_ERR(copy_lines_to_target(prop_target->content, 0,
+                                       scratch_pool));
+          if (! prop_target->content->eof)
+            {
+              /* We could not copy the entire target property to the
+               * temporary stream, and would truncate the target if we
+               * copied the temporary stream on top of it. Skip this target. */
+              prop_target->skipped = TRUE;
+            }
+        }
       }
 
   svn_pool_destroy(iterpool);
@@ -2476,57 +2954,9 @@ apply_one_patch(patch_target_t **patch_t
        * will be closed later in write_out_rejected_hunks(). */
       if (target->kind_on_disk == svn_node_file)
         SVN_ERR(svn_io_file_close(target->file, scratch_pool));
-
-      SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
     }
 
-  if (! target->skipped)
-    {
-      apr_finfo_t working_file;
-      apr_finfo_t patched_file;
-
-      /* Get sizes of the patched temporary file and the working file.
-       * We'll need those to figure out whether we should delete the
-       * patched file. */
-      SVN_ERR(svn_io_stat(&patched_file, target->patched_path,
-                          APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
-      if (target->kind_on_disk == svn_node_file)
-        SVN_ERR(svn_io_stat(&working_file, target->local_abspath,
-                            APR_FINFO_SIZE | APR_FINFO_LINK, scratch_pool));
-      else
-        working_file.size = 0;
-
-      if (patched_file.size == 0 && working_file.size > 0)
-        {
-          /* If a unidiff removes all lines from a file, that usually
-           * means deletion, so we can confidently schedule the target
-           * for deletion. In the rare case where the unidiff was really
-           * meant to replace a file with an empty one, this may not
-           * be desirable. But the deletion can easily be reverted and
-           * creating an empty file manually is not exactly hard either. */
-          target->deleted = (target->db_kind == svn_node_file);
-        }
-      else if (patched_file.size == 0 && working_file.size == 0)
-        {
-          /* The target was empty or non-existent to begin with
-           * and no content was changed by patching.
-           * Report this as skipped if it didn't exist, unless in the special
-           * case of adding an empty file which has properties set on it or
-           * adding an empty file with a 'git diff' */
-          if (target->kind_on_disk == svn_node_none
-              && ! target->has_prop_changes
-              && ! target->added)
-            target->skipped = TRUE;
-        }
-      else if (patched_file.size > 0 && working_file.size == 0)
-        {
-          /* The patch has created a file. */
-          if (target->locally_deleted)
-            target->replaced = TRUE;
-          else if (target->db_kind == svn_node_none)
-            target->added = TRUE;
-        }
-    }
+  SVN_ERR(svn_io_file_close(target->patched_file, scratch_pool));
 
   *patch_target = target;
 
@@ -2536,6 +2966,9 @@ apply_one_patch(patch_target_t **patch_t
 /* Try to create missing parent directories for TARGET in the working copy
  * rooted at ABS_WC_PATH, and add the parents to version control.
  * If the parents cannot be created, mark the target as skipped.
+ *
+ * In dry run mode record missing parents in ALREADY_ADDED
+ *
  * Use client context CTX. If DRY_RUN is true, do not create missing
  * parents but issue notifications only.
  * Use SCRATCH_POOL for temporary allocations. */
@@ -2544,6 +2977,7 @@ create_missing_parents(patch_target_t *t
                        const char *abs_wc_path,
                        svn_client_ctx_t *ctx,
                        svn_boolean_t dry_run,
+                       apr_array_header_t *targets_info,
                        apr_pool_t *scratch_pool)
 {
   const char *local_abspath;
@@ -2624,12 +3058,28 @@ create_missing_parents(patch_target_t *t
       for (i = present_components; i < components->nelts - 1; i++)
         {
           const char *component;
+          patch_target_info_t *pti;
 
           svn_pool_clear(iterpool);
 
+          if (ctx->cancel_func)
+            SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+
           component = APR_ARRAY_IDX(components, i, const char *);
           local_abspath = svn_dirent_join(local_abspath, component,
                                           scratch_pool);
+
+          if (target_is_added(targets_info, local_abspath, iterpool))
+            continue;
+
+          pti = apr_pcalloc(targets_info->pool, sizeof(*pti));
+
+          pti->local_abspath = apr_pstrdup(targets_info->pool,
+                                           local_abspath);
+          pti->added = TRUE;
+
+          APR_ARRAY_PUSH(targets_info, patch_target_info_t *) = pti;
+
           if (dry_run)
             {
               if (ctx->notify_func2)
@@ -2650,10 +3100,6 @@ create_missing_parents(patch_target_t *t
                * to version control. Allow cancellation since we
                * have not modified the working copy yet for this
                * target. */
-
-              if (ctx->cancel_func)
-                SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
-
               SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, local_abspath,
                                             NULL /*props*/,
                                             FALSE /* skip checks */,
@@ -2669,11 +3115,17 @@ create_missing_parents(patch_target_t *t
 
 /* Install a patched TARGET into the working copy at ABS_WC_PATH.
  * Use client context CTX to retrieve WC_CTX, and possibly doing
- * notifications. If DRY_RUN is TRUE, don't modify the working copy.
+ * notifications.
+ *
+ * Pass on ALREADY_ADDED to allow recording already added ancestors
+ * in dry-run mode.
+ *
+ * If DRY_RUN is TRUE, don't modify the working copy.
  * Do temporary allocations in POOL. */
 static svn_error_t *
 install_patched_target(patch_target_t *target, const char *abs_wc_path,
                        svn_client_ctx_t *ctx, svn_boolean_t dry_run,
+                       apr_array_header_t *targets_info,
                        apr_pool_t *pool)
 {
   if (target->deleted)
@@ -2687,13 +3139,15 @@ install_patched_target(patch_target_t *t
            * notify about what we did before aborting. */
           SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->local_abspath,
                                  FALSE /* keep_local */, FALSE,
-                                 NULL, NULL, NULL, NULL, pool));
+                                 ctx->cancel_func, ctx->cancel_baton,
+                                 NULL, NULL /* notify */,
+                                 pool));
         }
     }
   else
     {
       svn_node_kind_t parent_db_kind;
-      if (target->added || target->replaced)
+      if (target->added)
         {
           const char *parent_abspath;
 
@@ -2723,7 +3177,7 @@ install_patched_target(patch_target_t *t
             }
           else
             SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
-                                           dry_run, pool));
+                                           dry_run, targets_info, pool));
 
         }
       else
@@ -2739,6 +3193,8 @@ install_patched_target(patch_target_t *t
               || wc_kind != target->kind_on_disk)
             {
               target->skipped = TRUE;
+              if (wc_kind != target->kind_on_disk)
+                target->obstructed = TRUE;
             }
         }
 
@@ -2755,6 +3211,8 @@ install_patched_target(patch_target_t *t
               SVN_ERR(svn_subst_create_specialfile(&stream,
                                                    target->local_abspath,
                                                    pool, pool));
+              if (target->git_symlink_format)
+                  SVN_ERR(svn_stream_puts(stream, "link "));
               SVN_ERR(svn_stream_copy3(patched_stream, stream,
                                        ctx->cancel_func, ctx->cancel_baton,
                                        pool));
@@ -2782,7 +3240,7 @@ install_patched_target(patch_target_t *t
                         ctx->cancel_func, ctx->cancel_baton, pool));
             }
 
-          if (target->added || target->replaced)
+          if (target->added)
             {
               /* The target file didn't exist previously,
                * so add it to version control.
@@ -2816,7 +3274,8 @@ install_patched_target(patch_target_t *t
                                     target->move_target_abspath,
                                     TRUE, /* metadata_only */
                                     FALSE, /* allow_mixed_revisions */
-                                    NULL, NULL, NULL, NULL,
+                                    ctx->cancel_func, ctx->cancel_baton,
+                                    NULL, NULL,
                                     pool));
 
               /* Delete the patch target's old location from disk. */
@@ -2835,19 +3294,36 @@ install_patched_target(patch_target_t *t
 static svn_error_t *
 write_out_rejected_hunks(patch_target_t *target,
                          svn_boolean_t dry_run,
-                         apr_pool_t *pool)
+                         apr_pool_t *scratch_pool)
 {
-  SVN_ERR(svn_io_file_close(target->reject_file, pool));
-
   if (! dry_run && (target->had_rejects || target->had_prop_rejects))
     {
       /* Write out rejected hunks, if any. */
-      SVN_ERR(svn_io_copy_file(target->reject_path,
-                               apr_psprintf(pool, "%s.svnpatch.rej",
-                               target->local_abspath),
-                               FALSE, pool));
+      apr_file_t *reject_file;
+
+      SVN_ERR(svn_io_open_uniquely_named(&reject_file, NULL,
+                                         svn_dirent_dirname(
+                                              target->local_abspath,
+                                              scratch_pool),
+                                         svn_dirent_basename(
+                                              target->local_abspath,
+                                              NULL),
+                                         ".svnpatch.rej",
+                                         svn_io_file_del_none,
+                                         scratch_pool, scratch_pool));
+
+      SVN_ERR(svn_stream_reset(target->reject_stream));
+
+      /* svn_stream_copy3() closes the files for us */
+      SVN_ERR(svn_stream_copy3(target->reject_stream,
+                                  svn_stream_from_aprfile2(reject_file, FALSE,
+                                                           scratch_pool),
+                                  NULL, NULL, scratch_pool));
       /* ### TODO mark file as conflicted. */
     }
+  else
+    SVN_ERR(svn_stream_close(target->reject_stream));
+
   return SVN_NO_ERROR;
 }
 
@@ -2861,6 +3337,13 @@ install_patched_prop_targets(patch_targe
 {
   apr_hash_index_t *hi;
   apr_pool_t *iterpool;
+  const char *local_abspath;
+
+  /* Apply properties to a move target if there is one */
+  if (target->move_target_abspath)
+    local_abspath = target->move_target_abspath;
+  else
+    local_abspath = target->local_abspath;
 
   iterpool = svn_pool_create(scratch_pool);
 
@@ -2877,11 +3360,14 @@ install_patched_prop_targets(patch_targe
       if (ctx->cancel_func)
         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
 
+      if (prop_target->skipped)
+        continue;
+
       /* For a deleted prop we only set the value to NULL. */
       if (prop_target->operation == svn_diff_op_deleted)
         {
           if (! dry_run)
-            SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
+            SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
                                      prop_target->name, NULL, svn_depth_empty,
                                      TRUE /* skip_checks */,
                                      NULL /* changelist_filter */,
@@ -2891,31 +3377,6 @@ install_patched_prop_targets(patch_targe
           continue;
         }
 
-      /* If the patch target doesn't exist yet, the patch wants to add an
-       * empty file with properties set on it. So create an empty file and
-       * add it to version control. But if the patch was in the 'git format'
-       * then the file has already been added.
-       *
-       * ### How can we tell whether the patch really wanted to create
-       * ### an empty directory? */
-      if (! target->has_text_changes
-          && target->kind_on_disk == svn_node_none
-          && ! target->added)
-        {
-          if (! dry_run)
-            {
-              SVN_ERR(svn_io_file_create_empty(target->local_abspath,
-                                               scratch_pool));
-              SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, target->local_abspath,
-                                            NULL /*props*/,
-                                            FALSE /* skip checks */,
-                                            /* suppress notification */
-                                            NULL, NULL,
-                                            iterpool));
-            }
-          target->added = TRUE;
-        }
-
       /* Attempt to set the property, and reject all hunks if this
          fails.  If the property had a non-empty value, but now has
          an empty one, we'll just delete the property altogether.  */
@@ -2931,7 +3392,7 @@ install_patched_prop_targets(patch_targe
 
           err = svn_wc_canonicalize_svn_prop(&canon_propval,
                                              prop_target->name,

[... 93 lines stripped ...]