You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by hw...@apache.org on 2010/08/10 20:06:33 UTC

svn commit: r984153 [16/39] - in /subversion/branches/ignore-mergeinfo: ./ build/ build/ac-macros/ build/generator/ build/generator/templates/ build/hudson/ build/hudson/jobs/subversion-1.6.x-solaris/ build/hudson/jobs/subversion-1.6.x-ubuntu/ build/hu...

Modified: subversion/branches/ignore-mergeinfo/subversion/libsvn_client/patch.c
URL: http://svn.apache.org/viewvc/subversion/branches/ignore-mergeinfo/subversion/libsvn_client/patch.c?rev=984153&r1=984152&r2=984153&view=diff
==============================================================================
--- subversion/branches/ignore-mergeinfo/subversion/libsvn_client/patch.c (original)
+++ subversion/branches/ignore-mergeinfo/subversion/libsvn_client/patch.c Tue Aug 10 18:06:17 2010
@@ -28,16 +28,19 @@
 /*** Includes. ***/
 
 #include <apr_hash.h>
+#include <apr_fnmatch.h>
 #include "svn_client.h"
 #include "svn_dirent_uri.h"
+#include "svn_diff.h"
 #include "svn_io.h"
 #include "svn_path.h"
 #include "svn_pools.h"
+#include "svn_props.h"
 #include "svn_subst.h"
 #include "svn_wc.h"
+#include "client.h"
 
 #include "svn_private_config.h"
-#include "private/svn_diff_private.h"
 #include "private/svn_eol_private.h"
 #include "private/svn_wc_private.h"
 
@@ -47,6 +50,13 @@ typedef struct {
 
   /* The line where the hunk matched in the target file. */
   svn_linenum_t matched_line;
+
+  /* Whether this hunk has been rejected. */
+  svn_boolean_t rejected;
+
+  /* The fuzz factor used when matching this hunk, i.e. how many
+   * lines of leading and trailing context to ignore during matching. */
+  int fuzz;
 } hunk_info_t;
 
 typedef struct {
@@ -60,7 +70,7 @@ typedef struct {
   /* The target path, relative to the working copy directory the
    * patch is being applied to. A patch strip count applies to this
    * and only this path. This is never NULL. */
-  const char *wc_path;
+  const char *rel_path;
 
   /* The absolute path of the target on the filesystem.
    * Any symlinks the path from the patch file may contain are resolved.
@@ -82,28 +92,29 @@ typedef struct {
    * so EOL transformation and keyword contraction is done transparently. */
   svn_stream_t *patched;
 
-  /* The patched stream, without EOL transformation and keyword contraction. */
+  /* The patched stream, without EOL transformation and keyword expansion. */
   svn_stream_t *patched_raw;
 
   /* Path to the temporary file underlying the result stream. */
   const char *patched_path;
 
   /* The reject stream, write-only, not seekable.
-   * Hunks that are rejected will be written to this stream.
-   * The data in the underlying file needs to be in repository-normal form,
-   * so EOL transformation and keyword contraction is done transparently. */
+   * Hunks that are rejected will be written to this stream. */
   svn_stream_t *reject;
 
-  /* The reject stream, without EOL transformation and keyword contraction. */
-  svn_stream_t *reject_raw;
-
   /* Path to the temporary file underlying the reject stream. */
   const char *reject_path;
 
   /* The line last read from the target file. */
   svn_linenum_t current_line;
 
-  /* EOL-marker used by target file. */
+  /* The EOL-style of the target. Either 'none', 'fixed', or 'native'.
+   * See the documentation of svn_subst_eol_style_t. */
+  svn_subst_eol_style_t eol_style;
+
+  /* If the EOL_STYLE above is not 'none', this is the EOL string
+   * corresponding to the EOL-style. Else, it is the EOL string the
+   * last line read from the target file was using. */
   const char *eol_str;
 
   /* An array containing stream markers marking the beginning
@@ -111,7 +122,7 @@ typedef struct {
   apr_array_header_t *lines;
 
   /* An array containing hunk_info_t structures for hunks already matched. */
-  apr_array_header_t *matched_hunks;
+  apr_array_header_t *hunks;
 
   /* The node kind of the target as found on disk prior
    * to patch application. */
@@ -123,10 +134,8 @@ typedef struct {
   /* True if the target had to be skipped for some reason. */
   svn_boolean_t skipped;
 
-  /* True if at least one hunk was applied to the target.
-   * The hunk may have been a no-op, however (e.g. a hunk trying
-   * to delete a line from an empty file). */
-  svn_boolean_t modified;
+  /* True if the target matches a filter glob pattern. */
+  svn_boolean_t filtered;
 
   /* True if at least one hunk was rejected. */
   svn_boolean_t had_rejects;
@@ -135,9 +144,8 @@ typedef struct {
    * patch was applied to it. */
   svn_boolean_t local_mods;
 
-  /* True if the target was added by the patch, which means that it
-   * did not exist on disk before patching and does exist on disk
-   * after patching. */
+  /* 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. */
   svn_boolean_t added;
 
   /* True if the target ended up being deleted by the patch. */
@@ -146,6 +154,12 @@ typedef struct {
   /* True if the target has the executable bit set. */
   svn_boolean_t executable;
 
+  /* True if the target's parent directory exists. */
+  svn_boolean_t parent_dir_exists;
+
+  /* The keywords of the target. */
+  apr_hash_t *keywords;
+
   /* The pool the target is allocated in. */
   apr_pool_t *pool;
 } patch_target_t;
@@ -189,7 +203,8 @@ strip_path(const char **result, const ch
  * Put a canonicalized version of PATH_FROM_PATCHFILE into
  * TARGET->CANON_PATH_FROM_PATCHFILE.
  * WC_CTX is a context for the working copy the patch is applied to.
- * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, and TARGET->KIND.
+ * If possible, determine TARGET->WC_PATH, TARGET->ABS_PATH, TARGET->KIND,
+ * TARGET->ADDED, and TARGET->PARENT_DIR_EXISTS.
  * 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.
@@ -197,14 +212,14 @@ strip_path(const char **result, const ch
 static svn_error_t *
 resolve_target_path(patch_target_t *target,
                     const char *path_from_patchfile,
-                    const char *wc_path,
+                    const char *abs_wc_path,
                     int strip_count,
                     svn_wc_context_t *wc_ctx,
                     apr_pool_t *result_pool,
                     apr_pool_t *scratch_pool)
 {
-  const char *abs_wc_path;
   const char *stripped_path;
+  svn_wc_status2_t *status;
 
   target->canon_path_from_patchfile = svn_dirent_internal_style(
                                         path_from_patchfile, result_pool);
@@ -214,12 +229,10 @@ resolve_target_path(patch_target_t *targ
       target->skipped = TRUE;
       target->kind = svn_node_file;
       target->abs_path = NULL;
-      target->wc_path = "";
+      target->rel_path = "";
       return SVN_NO_ERROR;
     }
 
-  SVN_ERR(svn_dirent_get_absolute(&abs_wc_path, wc_path, scratch_pool));
-
   if (strip_count > 0)
     SVN_ERR(strip_path(&stripped_path, target->canon_path_from_patchfile,
                        strip_count, result_pool, scratch_pool));
@@ -228,28 +241,28 @@ resolve_target_path(patch_target_t *targ
 
   if (svn_dirent_is_absolute(stripped_path))
     {
-      target->wc_path = svn_dirent_is_child(abs_wc_path, stripped_path,
-                                            result_pool);
-      if (! target->wc_path)
+      target->rel_path = svn_dirent_is_child(abs_wc_path, stripped_path,
+                                             result_pool);
+      if (! target->rel_path)
         {
           /* The target path is either outside of the working copy
            * or it is the working copy itself. Skip it. */
           target->skipped = TRUE;
           target->kind = svn_node_file;
           target->abs_path = NULL;
-          target->wc_path = stripped_path;
+          target->rel_path = stripped_path;
           return SVN_NO_ERROR;
         }
     }
   else
     {
-      target->wc_path = stripped_path;
+      target->rel_path = stripped_path;
     }
 
   /* 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. */
   if (! svn_dirent_is_under_root(&target->abs_path, abs_wc_path,
-                                 target->wc_path, result_pool))
+                                 target->rel_path, result_pool))
     {
       /* The target path is outside of the working copy. Skip it. */
       target->skipped = TRUE;
@@ -258,25 +271,44 @@ resolve_target_path(patch_target_t *targ
       return SVN_NO_ERROR;
     }
 
-  /* Find out if there is a suitable patch target at the target path,
-   * and determine if the target should be skipped. */
-  SVN_ERR(svn_io_check_path(target->abs_path, &target->kind, scratch_pool));
+  /* Skip things we should not be messing with. */
+  SVN_ERR(svn_wc_status3(&status, wc_ctx, target->abs_path,
+                         scratch_pool, scratch_pool));
+  if (status->text_status == svn_wc_status_unversioned ||
+      status->text_status == svn_wc_status_ignored ||
+      status->text_status == svn_wc_status_obstructed)
+    {
+      target->skipped = TRUE;
+      SVN_ERR(svn_io_check_path(target->abs_path, &target->kind,
+                                scratch_pool));
+      return SVN_NO_ERROR;
+    }
+
+  SVN_ERR(svn_wc__node_get_kind(&target->kind, wc_ctx, target->abs_path,
+                                FALSE, scratch_pool));
   switch (target->kind)
     {
       case svn_node_file:
-        target->skipped = FALSE;
+        target->parent_dir_exists = TRUE;
         break;
       case svn_node_none:
+      case svn_node_unknown:
         {
-          const char *dirname;
+          const char *abs_dirname;
           svn_node_kind_t kind;
 
           /* The file is not there, that's fine. The patch might want to
-           * create it. But the containing directory of the target needs
-           * to exists. Otherwise we won't be able to apply the patch. */
-          dirname = svn_dirent_dirname(target->abs_path, scratch_pool);
-          SVN_ERR(svn_io_check_path(dirname, &kind, scratch_pool));
-          target->skipped = (kind != svn_node_dir);
+           * create it. Check if the containing directory of the target
+           * exists. We may need to create it later. */
+          abs_dirname = svn_dirent_dirname(target->abs_path, scratch_pool);
+          SVN_ERR(svn_wc__node_get_kind(&kind, wc_ctx, abs_dirname,
+                                        FALSE, scratch_pool));
+          SVN_ERR(svn_wc_status3(&status, wc_ctx, abs_dirname,
+                                 scratch_pool, scratch_pool));
+          target->parent_dir_exists =
+            (kind == svn_node_dir &&
+             status->text_status != svn_wc_status_deleted &&
+             status->text_status != svn_wc_status_missing);
           break;
         }
       default:
@@ -284,185 +316,230 @@ resolve_target_path(patch_target_t *targ
         break;
     }
 
-  if (! target->skipped)
-    {
-      const svn_wc_entry_t *entry;
-
-      /* If the target is versioned, we should be able to get an entry. */
-      SVN_ERR(svn_wc__maybe_get_entry(&entry, wc_ctx,
-                                      target->abs_path,
-                                      svn_node_unknown,
-                                      TRUE, FALSE,
-                                      result_pool,
-                                      scratch_pool));
-      if (entry)
-        {
-          if (entry->schedule == svn_wc_schedule_delete)
-            {
-              /* The target is versioned and scheduled for deletion,
-               * skip it. */
-              target->skipped = TRUE;
-            }
-        }
-      else if (target->kind == svn_node_file)
-        {
-          /* The target is an unversioned file, skip it. */
-           target->skipped = TRUE;
-        }
-    }
-
   return SVN_NO_ERROR;
 }
 
-/* Indicate in *LOCAL_MODS whether the file at LOCAL_ABSPATH, has local
-   modifications. */
-static svn_error_t *
-check_local_mods(svn_boolean_t *local_mods,
-                 svn_wc_context_t *wc_ctx,
-                 const char *local_abspath,
-                 apr_pool_t *pool)
-{
-  svn_error_t *err;
-
-  err = svn_wc_text_modified_p2(local_mods, wc_ctx, local_abspath, FALSE,
-                                pool);
-  if (err)
-    {
-      if (err->apr_err == SVN_ERR_ENTRY_NOT_FOUND)
-        {
-          /* The target file is not versioned, that's OK.
-           * We can treat it as unmodified. */
-          svn_error_clear(err);
-          *local_mods = FALSE;
-        }
-      else
-        return svn_error_return(err);
-    }
-
-  return SVN_NO_ERROR;
-}
-
-/* Attempt to initialize a patch TARGET structure for a target file
- * described by PATCH.
- * Use client context CTX to send notifiations and retrieve WC_CTX.
+/* Attempt to initialize a *PATCH_TARGET structure for a target file
+ * described by PATCH. Use working copy context WC_CTX.
  * STRIP_COUNT specifies the number of leading path components
  * which should be stripped from target paths in the patch.
  * Upon success, allocate the patch target structure in RESULT_POOL.
  * Else, set *target to NULL.
+ * If a target does not match a glob in INCLUDE_PATTERNS, mark it as filtered.
+ * If a target matches a glob in EXCLUDE_PATTERNS, mark it as filtered.
+ * If PATCHED_TEMPFILES or REJECT_TEMPFILES are not NULL, add the path
+ * to temporary patched/reject files to them, keyed by the target's path
+ * as parsed from the patch file (after canonicalization).
  * Use SCRATCH_POOL for all other allocations. */
 static svn_error_t *
-init_patch_target(patch_target_t **target,
+init_patch_target(patch_target_t **patch_target,
                   const svn_patch_t *patch,
                   const char *base_dir,
-                  const svn_client_ctx_t *ctx, int strip_count,
+                  svn_wc_context_t *wc_ctx, int strip_count,
+                  const apr_array_header_t *include_patterns,
+                  const apr_array_header_t *exclude_patterns,
+                  apr_hash_t *patched_tempfiles,
+                  apr_hash_t *reject_tempfiles,
                   apr_pool_t *result_pool, apr_pool_t *scratch_pool)
 {
-  patch_target_t *new_target;
+  patch_target_t *target;
 
-  *target = NULL;
-  new_target = apr_pcalloc(result_pool, sizeof(*new_target));
+  target = apr_pcalloc(result_pool, sizeof(*target));
 
-  SVN_ERR(resolve_target_path(new_target, patch->new_filename,
-                              base_dir, strip_count, ctx->wc_ctx,
+  SVN_ERR(resolve_target_path(target, patch->new_filename,
+                              base_dir, strip_count, wc_ctx,
                               result_pool, scratch_pool));
 
-  if (new_target->kind == svn_node_file && ! new_target->skipped)
+  target->filtered = FALSE;
+  if (include_patterns)
     {
-      /* Try to open the target file */
-      SVN_ERR(svn_io_file_open(&new_target->file, new_target->abs_path,
-                               APR_READ | APR_BINARY | APR_BUFFERED,
-                               APR_OS_DEFAULT, result_pool));
-      SVN_ERR(svn_eol__detect_file_eol(&new_target->eol_str, new_target->file,
-                                        scratch_pool));
-      new_target->stream = svn_stream_from_aprfile2(new_target->file, FALSE,
-                                                    result_pool);
-    }
+      int i;
+      const char *glob;
+      svn_boolean_t match;
 
-  if (new_target->eol_str == NULL)
+      match = FALSE;
+      for (i = 0; i < include_patterns->nelts; i++)
+        {
+          glob = APR_ARRAY_IDX(include_patterns, i, const char *);
+          match = (apr_fnmatch(glob, target->canon_path_from_patchfile,
+                               APR_FNM_CASE_BLIND) == APR_SUCCESS);
+          if (match)
+            break;
+        }
+
+      if (! match)
+        {
+          target->filtered = TRUE;
+          *patch_target = target;
+          return SVN_NO_ERROR;
+        }
+    }
+  if (exclude_patterns)
     {
-      /* Either we couldn't figure out the target files's EOL scheme,
-       * or the target file doesn't exist. Just use native EOL makers. */
-      new_target->eol_str = APR_EOL_STR;
+      int i;
+      const char *glob;
+
+      for (i = 0; i < exclude_patterns->nelts; i++)
+        {
+          glob = APR_ARRAY_IDX(exclude_patterns, i, const char *);
+          target->filtered = (apr_fnmatch(glob,
+                                          target->canon_path_from_patchfile,
+                                          APR_FNM_CASE_BLIND) == APR_SUCCESS);
+          if (target->filtered)
+            {
+              *patch_target = target;
+              return SVN_NO_ERROR;
+            }
+        }
     }
 
-  new_target->local_mods = FALSE;
-  new_target->executable = FALSE;
+  target->local_mods = FALSE;
+  target->executable = FALSE;
 
-  if (! new_target->skipped)
+  if (! target->skipped)
     {
-      apr_hash_t *keywords;
+      apr_hash_t *props;
+      svn_string_t *keywords_val;
+      svn_string_t *eol_style_val;
       const char *diff_header;
+      svn_boolean_t repair_eol;
       apr_size_t len;
 
-      /* TODO: Get keywords from patch target. How?
-       * Can't call svn_wc__get_keywords() here? */
-      keywords = apr_hash_make(result_pool);
+      target->keywords = NULL;
+      target->eol_style = svn_subst_eol_style_none;
+      target->eol_str = NULL;
 
-      /* Create a temporary file to write the patched result to. */
-      SVN_ERR(svn_stream_open_unique(&new_target->patched_raw,
-                                     &new_target->patched_path, NULL,
-                                     svn_io_file_del_on_pool_cleanup,
-                                     result_pool, scratch_pool));
-      new_target->patched = svn_subst_stream_translated(new_target->patched_raw,
-                                                        "\n", TRUE,
-                                                        keywords, FALSE,
-                                                        result_pool);
-
-      SVN_ERR(check_local_mods(&new_target->local_mods, ctx->wc_ctx,
-                               new_target->abs_path, scratch_pool));
-
-      if (new_target->kind == svn_node_file)
-        SVN_ERR(svn_io_is_file_executable(&new_target->executable,
-                                          new_target->abs_path,
+      if (target->kind == svn_node_file)
+        {
+          /* Open the file. */
+          SVN_ERR(svn_io_file_open(&target->file, target->abs_path,
+                                   APR_READ | APR_BINARY | APR_BUFFERED,
+                                   APR_OS_DEFAULT, result_pool));
+
+          /* Handle svn:keyword and svn:eol-style properties. */
+          SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, target->abs_path,
+                                    scratch_pool, scratch_pool));
+          keywords_val = apr_hash_get(props, SVN_PROP_KEYWORDS,
+                                      APR_HASH_KEY_STRING);
+          if (keywords_val)
+            {
+              svn_revnum_t changed_rev;
+              apr_time_t changed_date;
+              const char *rev_str;
+              const char *author;
+              const char *url;
+
+              SVN_ERR(svn_wc__node_get_changed_info(&changed_rev,
+                                                    &changed_date,
+                                                    &author, wc_ctx,
+                                                    target->abs_path,
+                                                    scratch_pool,
+                                                    scratch_pool));
+              rev_str = apr_psprintf(scratch_pool, "%"SVN_REVNUM_T_FMT,
+                                     changed_rev);
+              SVN_ERR(svn_wc__node_get_url(&url, wc_ctx,
+                                           target->abs_path,
+                                           scratch_pool, scratch_pool));
+              SVN_ERR(svn_subst_build_keywords2(&target->keywords,
+                                                keywords_val->data,
+                                                rev_str, url, changed_date,
+                                                author, result_pool));
+            }
+
+          eol_style_val = apr_hash_get(props, SVN_PROP_EOL_STYLE,
+                                       APR_HASH_KEY_STRING);
+          if (eol_style_val)
+            {
+              svn_subst_eol_style_from_value(&target->eol_style,
+                                             &target->eol_str,
+                                             eol_style_val->data);
+            }
+
+          /* Create a stream to read from the target. */
+          target->stream = svn_stream_from_aprfile2(target->file,
+                                                    FALSE, result_pool);
+
+          /* Also check the file for local mods and the Xbit. */
+          SVN_ERR(svn_wc_text_modified_p2(&target->local_mods, wc_ctx,
+                                          target->abs_path, FALSE,
                                           scratch_pool));
+          SVN_ERR(svn_io_is_file_executable(&target->executable,
+                                            target->abs_path,
+                                            scratch_pool));
+        }
 
-      /* We'll also need a stream to write rejected hunks to. */
-      SVN_ERR(svn_stream_open_unique(&new_target->reject_raw,
-                                     &new_target->reject_path, NULL,
-                                     svn_io_file_del_on_pool_cleanup,
+      /* Create a temporary file to write the patched result to. */
+      SVN_ERR(svn_stream_open_unique(&target->patched_raw,
+                                     &target->patched_path, NULL,
+                                     patched_tempfiles ?
+                                       svn_io_file_del_none :
+                                       svn_io_file_del_on_pool_cleanup,
                                      result_pool, scratch_pool));
-      new_target->reject = svn_subst_stream_translated(
-                               new_target->reject_raw, "\n",
-                               TRUE, keywords, FALSE, result_pool);
+      if (patched_tempfiles)
+        apr_hash_set(patched_tempfiles, target->canon_path_from_patchfile,
+                     APR_HASH_KEY_STRING, target->patched_path);
+
+      /* Expand keywords in the patched file.
+       * Repair newlines if svn:eol-style dictates a particular style. */
+      repair_eol = (target->eol_style == svn_subst_eol_style_fixed ||
+                    target->eol_style == svn_subst_eol_style_native);
+      target->patched = svn_subst_stream_translated(
+                              target->patched_raw, target->eol_str, repair_eol,
+                              target->keywords, TRUE, result_pool);
+
+      /* We'll also need a stream to write rejected hunks to.
+       * We don't expand keywords, nor normalise line-endings,
+       * in reject files. */
+      SVN_ERR(svn_stream_open_unique(&target->reject,
+                                     &target->reject_path, NULL,
+                                     reject_tempfiles ?
+                                       svn_io_file_del_none :
+                                       svn_io_file_del_on_pool_cleanup,
+                                     result_pool, scratch_pool));
+      if (reject_tempfiles)
+        apr_hash_set(reject_tempfiles, target->canon_path_from_patchfile,
+                     APR_HASH_KEY_STRING, target->reject_path);
+
+      /* The reject stream needs a diff header. */
       diff_header = apr_psprintf(scratch_pool, "--- %s%s+++ %s%s",
-                                 new_target->canon_path_from_patchfile,
-                                 new_target->eol_str,
-                                 new_target->canon_path_from_patchfile,
-                                 new_target->eol_str);
+                                 target->canon_path_from_patchfile,
+                                 APR_EOL_STR,
+                                 target->canon_path_from_patchfile,
+                                 APR_EOL_STR);
       len = strlen(diff_header);
-      SVN_ERR(svn_stream_write(new_target->reject, diff_header, &len));
+      SVN_ERR(svn_stream_write(target->reject, diff_header, &len));
     }
 
-  new_target->patch = patch;
-  new_target->current_line = 1;
-  new_target->modified = FALSE;
-  new_target->had_rejects = FALSE;
-  new_target->added = FALSE;
-  new_target->deleted = FALSE;
-  new_target->eof = FALSE;
-  new_target->pool = result_pool;
-  new_target->lines = apr_array_make(result_pool, 0,
-                                     sizeof(svn_stream_mark_t *));
-  new_target->matched_hunks = apr_array_make(result_pool, 0,
-                                             sizeof(hunk_info_t *));
-  *target = new_target;
+  target->patch = patch;
+  target->current_line = 1;
+  target->had_rejects = FALSE;
+  target->deleted = FALSE;
+  target->eof = FALSE;
+  target->pool = result_pool;
+  target->lines = apr_array_make(result_pool, 0,
+                                 sizeof(svn_stream_mark_t *));
+  target->hunks = apr_array_make(result_pool, 0, sizeof(hunk_info_t *));
+
+  *patch_target = target;
   return SVN_NO_ERROR;
 }
 
-/* Read a line from TARGET. If this line has not been read before
- * mark the line in TARGET->LINES.
- * Return the line in *STRINGBUF, allocated in RESULT_POOL.
+/* Read a *LINE from TARGET. If the line has not been read before
+ * mark the line in TARGET->LINES. Allocate *LINE in RESULT_POOL.
  * Do temporary allocations in SCRATCH_POOL.
  */
 static svn_error_t *
 read_line(patch_target_t *target,
-          svn_stringbuf_t **stringbuf,
+          const char **line,
           apr_pool_t *scratch_pool,
           apr_pool_t *result_pool)
 {
+  svn_stringbuf_t *line_raw;
+  const char *eol_str;
+
   if (target->eof)
     {
-      *stringbuf = svn_stringbuf_create_ensure(0, result_pool);
+      *line = "";
       return SVN_NO_ERROR;
     }
 
@@ -474,9 +551,19 @@ read_line(patch_target_t *target,
       APR_ARRAY_PUSH(target->lines, svn_stream_mark_t *) = mark;
     }
 
-  SVN_ERR(svn_stream_readline(target->stream, stringbuf, target->eol_str,
-                              &target->eof, result_pool));
-  target->current_line++;
+  SVN_ERR(svn_stream_readline_detect_eol(target->stream, &line_raw,
+                                         &eol_str, &target->eof,
+                                         scratch_pool));
+  if (target->eol_style == svn_subst_eol_style_none)
+    target->eol_str = eol_str;
+
+  /* Contract keywords. */
+  SVN_ERR(svn_subst_translate_cstring2(line_raw->data, line,
+                                       NULL, FALSE,
+                                       target->keywords, FALSE,
+                                       result_pool));
+  if (! target->eof)
+    target->current_line++;
 
   return SVN_NO_ERROR;
 }
@@ -489,11 +576,17 @@ static svn_error_t *
 seek_to_line(patch_target_t *target, svn_linenum_t line,
              apr_pool_t *scratch_pool)
 {
+  svn_linenum_t saved_line;
+  svn_boolean_t saved_eof;
+
   SVN_ERR_ASSERT(line > 0);
 
   if (line == target->current_line)
     return SVN_NO_ERROR;
 
+  saved_line = target->current_line;
+  saved_eof = target->eof;
+
   if (line <= target->lines->nelts)
     {
       svn_stream_mark_t *mark;
@@ -504,10 +597,10 @@ seek_to_line(patch_target_t *target, svn
     }
   else
     {
-      svn_stringbuf_t *dummy;
+      const char *dummy;
       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
 
-      while (target->current_line < line)
+      while (! target->eof && target->current_line < line)
         {
           svn_pool_clear(iterpool);
           SVN_ERR(read_line(target, &dummy, iterpool, iterpool));
@@ -515,20 +608,26 @@ seek_to_line(patch_target_t *target, svn
       svn_pool_destroy(iterpool);
     }
 
+  /* After seeking backwards from EOF position clear EOF indicator. */
+  if (saved_eof && saved_line > target->current_line)
+    target->eof = FALSE;
+
   return SVN_NO_ERROR;
 }
 
-/* Indicate in *MATCHED whether the original text of HUNK matches
- * the patch TARGET at its current line.
- * When this function returns, neither TARGET->CURRENT_LINE nor the
- * file offset in the target file will have changed.
- * HUNK->ORIGINAL_TEXT will be reset. Do temporary allocations in POOL. */
+/* Indicate in *MATCHED whether the original text of HUNK matches the patch
+ * TARGET at its current line. Lines within FUZZ lines of the start or end
+ * of HUNK will always match. When this function returns, neither
+ * TARGET->CURRENT_LINE nor the file offset in the target file will have
+ * changed. HUNK->ORIGINAL_TEXT will be reset.  Do temporary allocations in
+ * POOL. */
 static svn_error_t *
 match_hunk(svn_boolean_t *matched, patch_target_t *target,
-           const svn_hunk_t *hunk, apr_pool_t *pool)
+           const svn_hunk_t *hunk, int fuzz, apr_pool_t *pool)
 {
   svn_stringbuf_t *hunk_line;
-  svn_stringbuf_t *target_line;
+  const char *target_line;
+  svn_linenum_t lines_read;
   svn_linenum_t saved_line;
   svn_boolean_t hunk_eof;
   svn_boolean_t lines_matched;
@@ -540,20 +639,36 @@ match_hunk(svn_boolean_t *matched, patch
     return SVN_NO_ERROR;
 
   saved_line = target->current_line;
+  lines_read = 0;
   lines_matched = FALSE;
   SVN_ERR(svn_stream_reset(hunk->original_text));
   iterpool = svn_pool_create(pool);
   do
     {
+      const char *hunk_line_translated;
+
       svn_pool_clear(iterpool);
 
-      SVN_ERR(svn_stream_readline_detect_eol(hunk->original_text, &hunk_line,
-                                             NULL, &hunk_eof, iterpool));
+      SVN_ERR(svn_stream_readline_detect_eol(hunk->original_text,
+                                             &hunk_line, NULL,
+                                             &hunk_eof, iterpool));
+      /* Contract keywords, if any, before matching. */
+      SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
+                                           &hunk_line_translated,
+                                           NULL, FALSE,
+                                           target->keywords, FALSE,
+                                           iterpool));
+      lines_read++;
       SVN_ERR(read_line(target, &target_line, iterpool, iterpool));
       if (! hunk_eof)
         {
-          lines_matched = (hunk_line->len == target_line->len &&
-                           ! strcmp(hunk_line->data, target_line->data));
+          if (lines_read <= fuzz && hunk->leading_context > fuzz)
+            lines_matched = TRUE;
+          else if (lines_read > hunk->original_length - fuzz &&
+                   hunk->trailing_context > fuzz)
+            lines_matched = TRUE;
+          else
+            lines_matched = ! strcmp(hunk_line_translated, target_line);
         }
     }
   while (lines_matched && ! (hunk_eof || target->eof));
@@ -572,7 +687,6 @@ match_hunk(svn_boolean_t *matched, patch
         *matched = FALSE;
     }
   SVN_ERR(seek_to_line(target, saved_line, iterpool));
-  target->eof = FALSE;
 
   svn_pool_destroy(iterpool);
 
@@ -580,7 +694,7 @@ match_hunk(svn_boolean_t *matched, patch
 }
 
 /* Scan lines of TARGET for a match of the original text of HUNK,
- * up to but not including the specified UPPER_LINE.
+ * up to but not including the specified UPPER_LINE. Use fuzz factor FUZZ.
  * If UPPER_LINE is zero scan until EOF occurs when reading from TARGET.
  * Return the line at which HUNK was matched in *MATCHED_LINE.
  * If the hunk did not match at all, set *MATCHED_LINE to zero.
@@ -592,7 +706,7 @@ match_hunk(svn_boolean_t *matched, patch
 static svn_error_t *
 scan_for_match(svn_linenum_t *matched_line, patch_target_t *target,
                const svn_hunk_t *hunk, svn_boolean_t match_first,
-               svn_linenum_t upper_line, apr_pool_t *pool)
+               svn_linenum_t upper_line, int fuzz, apr_pool_t *pool)
 {
   apr_pool_t *iterpool;
 
@@ -606,18 +720,19 @@ scan_for_match(svn_linenum_t *matched_li
 
       svn_pool_clear(iterpool);
 
-      SVN_ERR(match_hunk(&matched, target, hunk, iterpool));
+      SVN_ERR(match_hunk(&matched, target, hunk, fuzz, iterpool));
       if (matched)
         {
           svn_boolean_t taken = FALSE;
 
           /* Don't allow hunks to match at overlapping locations. */
-          for (i = 0; i < target->matched_hunks->nelts; i++)
+          for (i = 0; i < target->hunks->nelts; i++)
             {
               const hunk_info_t *hi;
-              
-              hi = APR_ARRAY_IDX(target->matched_hunks, i, hunk_info_t *);
-              taken = (target->current_line >= hi->matched_line &&
+
+              hi = APR_ARRAY_IDX(target->hunks, i, const hunk_info_t *);
+              taken = (! hi->rejected &&
+                       target->current_line >= hi->matched_line &&
                        target->current_line < hi->matched_line +
                                               hi->hunk->original_length);
               if (taken)
@@ -632,7 +747,8 @@ scan_for_match(svn_linenum_t *matched_li
             }
         }
 
-      SVN_ERR(seek_to_line(target, target->current_line + 1, iterpool));
+      if (! target->eof)
+        SVN_ERR(seek_to_line(target, target->current_line + 1, iterpool));
     }
   svn_pool_destroy(iterpool);
 
@@ -641,33 +757,45 @@ scan_for_match(svn_linenum_t *matched_li
 
 /* Determine the line at which a HUNK applies to the TARGET file,
  * and return an appropriate hunk_info object in *HI, allocated from
- * RESULT_POOL. If no correct line can be determined,
- * set HI->MATCHED_LINE to zero.
- * When this function returns, neither TARGET->CURRENT_LINE nor the
- * file offset in the target file will have changed.
+ * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
+ * line can be determined, set HI->REJECTED to TRUE.
+ * When this function returns, neither TARGET->CURRENT_LINE nor the file
+ * offset in the target file will have changed.
  * Do temporary allocations in POOL. */
 static svn_error_t *
 get_hunk_info(hunk_info_t **hi, patch_target_t *target,
-              const svn_hunk_t *hunk, apr_pool_t *result_pool,
+              const svn_hunk_t *hunk, int fuzz, apr_pool_t *result_pool,
               apr_pool_t *scratch_pool)
 {
   svn_linenum_t matched_line;
 
   /* An original offset of zero means that this hunk wants to create
-   * a new file, potentially overwriting all content of an existing
-   * file in the WC. Don't bother matching hunks in that case, since
-   * the hunk applies at line 1. */
-  matched_line = 1;
-  if (hunk->original_start > 0 && target->kind == svn_node_file)
+   * a new file. Don't bother matching hunks in that case, since
+   * the hunk applies at line 1. If the file already exists, the hunk
+   * is rejected. */
+  if (hunk->original_start == 0)
+    {
+      if (target->kind == svn_node_file)
+        matched_line = 0;
+      else
+        matched_line = 1;
+    }
+  else if (hunk->original_start > 0 && target->kind == svn_node_file)
     {
       svn_linenum_t saved_line = target->current_line;
-      svn_boolean_t saved_eof = target->eof;
 
       /* Scan for a match at the line where the hunk thinks it
        * should be going. */
       SVN_ERR(seek_to_line(target, hunk->original_start, scratch_pool));
-      SVN_ERR(scan_for_match(&matched_line, target, hunk, TRUE,
-                             hunk->original_start + 1, scratch_pool));
+      if (target->current_line != hunk->original_start)
+        {
+          /* Seek failed. */
+          matched_line = 0;
+        }
+      else
+        SVN_ERR(scan_for_match(&matched_line, target, hunk, TRUE,
+                               hunk->original_start + 1, fuzz, scratch_pool));
+
       if (matched_line != hunk->original_start)
         {
           /* Scan the whole file again from the start. */
@@ -676,7 +804,7 @@ get_hunk_info(hunk_info_t **hi, patch_ta
           /* Scan forward towards the hunk's line and look for a line
            * where the hunk matches. */
           SVN_ERR(scan_for_match(&matched_line, target, hunk, FALSE,
-                                 hunk->original_start, scratch_pool));
+                                 hunk->original_start, fuzz, scratch_pool));
 
           /* In tie-break situations, we arbitrarily prefer early matches
            * to save us from scanning the rest of the file. */
@@ -685,17 +813,23 @@ get_hunk_info(hunk_info_t **hi, patch_ta
               /* Scan forward towards the end of the file and look
                * for a line where the hunk matches. */
               SVN_ERR(scan_for_match(&matched_line, target, hunk, TRUE, 0,
-                                     scratch_pool));
+                                     fuzz, scratch_pool));
             }
         }
 
       SVN_ERR(seek_to_line(target, saved_line, scratch_pool));
-      target->eof = saved_eof;
+    }
+  else
+    {
+      /* The hunk wants to modify a file which doesn't exist. */
+      matched_line = 0;
     }
 
   (*hi) = apr_palloc(result_pool, sizeof(hunk_info_t));
-  (*hi)->matched_line = matched_line;
   (*hi)->hunk = hunk;
+  (*hi)->matched_line = matched_line;
+  (*hi)->rejected = (matched_line == 0);
+  (*hi)->fuzz = fuzz;
 
   return SVN_NO_ERROR;
 }
@@ -732,120 +866,153 @@ copy_lines_to_target(patch_target_t *tar
   iterpool = svn_pool_create(pool);
   while ((target->current_line < line || line == 0) && ! target->eof)
     {
-      svn_stringbuf_t *target_line;
+      const char *target_line;
 
       svn_pool_clear(iterpool);
 
       SVN_ERR(read_line(target, &target_line, iterpool, iterpool));
       if (! target->eof)
-        svn_stringbuf_appendcstr(target_line, target->eol_str);
+        target_line = apr_pstrcat(iterpool, target_line, target->eol_str, NULL);
 
       SVN_ERR(try_stream_write(target->patched, target->patched_path,
-                               target_line->data, target_line->len, pool));
+                               target_line, strlen(target_line), iterpool));
     }
   svn_pool_destroy(iterpool);
 
   return SVN_NO_ERROR;
 }
 
-/* Copy HUNK_TEXT into TARGET, line by line, such that the line filter
- * and transformation callbacks set on HUNK_TEXT by the diff parsing
- * code in libsvn_diff will trigger. ABSPATH is the absolute path to the
- * file underlying TARGET. */
+/* Write the diff text of the hunk described by HI to the
+ * reject stream of TARGET, and mark TARGET as having had rejects.
+ * Do temporary allocations in POOL. */
 static svn_error_t *
-copy_hunk_text(svn_stream_t *hunk_text, svn_stream_t *target,
-               const char *abspath, apr_pool_t *scratch_pool)
+reject_hunk(patch_target_t *target, hunk_info_t *hi, apr_pool_t *pool)
 {
+  const char *hunk_header;
+  apr_size_t len;
   svn_boolean_t eof;
   apr_pool_t *iterpool;
 
-  iterpool = svn_pool_create(scratch_pool);
+  hunk_header = apr_psprintf(pool, "@@ -%lu,%lu +%lu,%lu @@%s",
+                             hi->hunk->original_start,
+                             hi->hunk->original_length,
+                             hi->hunk->modified_start,
+                             hi->hunk->modified_length,
+                             APR_EOL_STR);
+  len = strlen(hunk_header);
+  SVN_ERR(svn_stream_write(target->reject, hunk_header, &len));
+
+  iterpool = svn_pool_create(pool);
   do
     {
-      svn_stringbuf_t *line;
+      svn_stringbuf_t *hunk_line;
       const char *eol_str;
 
       svn_pool_clear(iterpool);
 
-      SVN_ERR(svn_stream_readline_detect_eol(hunk_text, &line, &eol_str,
-                                             &eof, iterpool));
+      SVN_ERR(svn_stream_readline_detect_eol(hi->hunk->diff_text, &hunk_line,
+                                             &eol_str, &eof, iterpool));
       if (! eof)
         {
-          if (line->len >= 1)
-            SVN_ERR(try_stream_write(target, abspath, line->data, line->len,
+          if (hunk_line->len >= 1)
+            SVN_ERR(try_stream_write(target->reject, target->reject_path,
+                                     hunk_line->data, hunk_line->len,
                                      iterpool));
           if (eol_str)
-            SVN_ERR(try_stream_write(target, abspath, eol_str, strlen(eol_str),
-                                     iterpool));
+            SVN_ERR(try_stream_write(target->reject, target->reject_path,
+                                     eol_str, strlen(eol_str), iterpool));
         }
     }
   while (! eof);
   svn_pool_destroy(iterpool);
 
-  return SVN_NO_ERROR;
-}
-
-
-static svn_error_t *
-reject_hunk(patch_target_t *target, const svn_hunk_t *hunk, apr_pool_t *pool)
-{
-  const char *hunk_header;
-  apr_size_t len;
-
-  hunk_header = apr_psprintf(pool, "@@ -%lu,%lu +%lu,%lu @@%s",
-                             hunk->original_start, hunk->original_length,
-                             hunk->modified_start, hunk->modified_length,
-                             target->eol_str);
-  len = strlen(hunk_header);
-  SVN_ERR(svn_stream_write(target->reject, hunk_header, &len));
-
-  SVN_ERR(copy_hunk_text(hunk->diff_text, target->reject,
-                         target->reject_path, pool));
-
   target->had_rejects = TRUE;
+
   return SVN_NO_ERROR;
 }
 
-/* Apply a HUNK to a patch TARGET. Do all allocations in POOL. */
+/* Write the modified text of hunk described by HI to the patched
+ * stream of TARGET. Do temporary allocations in POOL. */
 static svn_error_t *
-apply_one_hunk(const hunk_info_t *hi, patch_target_t *target, apr_pool_t *pool)
+apply_hunk(patch_target_t *target, hunk_info_t *hi, apr_pool_t *pool)
 {
+  svn_linenum_t lines_read;
+  svn_boolean_t eof;
+  apr_pool_t *iterpool;
+
   if (target->kind == svn_node_file)
     {
-      /* Move forward to the hunk's line, copying data as we go. */
-      SVN_ERR(copy_lines_to_target(target, hi->matched_line, pool));
-      if (target->eof)
+      svn_linenum_t line;
+
+      /* Move forward to the hunk's line, copying data as we go.
+       * Also copy leading lines of context which matched with fuzz.
+       * The target has changed on the fuzzy-matched lines,
+       * so we should retain the target's version of those lines. */
+      SVN_ERR(copy_lines_to_target(target, hi->matched_line + hi->fuzz,
+                                   pool));
+
+      /* Skip the target's version of the hunk.
+       * Don't skip trailing lines which matched with fuzz. */
+      line = target->current_line + hi->hunk->original_length - (2 * hi->fuzz);
+      SVN_ERR(seek_to_line(target, line, pool));
+      if (target->current_line != line)
         {
-          /* File is shorter than it should be. */
-          SVN_ERR(reject_hunk(target, hi->hunk, pool));
+          /* Seek failed, reject this hunk. */
+          hi->rejected = TRUE;
+          SVN_ERR(reject_hunk(target, hi, pool));
           return SVN_NO_ERROR;
         }
-
-      /* Skip the target's version of the hunk. */
-      SVN_ERR(seek_to_line(target,
-                           target->current_line + hi->hunk->original_length,
-                           pool));
     }
 
-  /* Copy the patched hunk text into the patched stream. */
-  SVN_ERR(copy_hunk_text(hi->hunk->modified_text, target->patched,
-                         target->patched_path, pool));
+  /* Write the hunk's version to the patched result.
+   * Don't write the lines which matched with fuzz. */
+  lines_read = 0;
+  iterpool = svn_pool_create(pool);
+  do
+    {
+      svn_stringbuf_t *hunk_line;
+      const char *eol_str;
+
+      svn_pool_clear(iterpool);
+
+      SVN_ERR(svn_stream_readline_detect_eol(hi->hunk->modified_text,
+                                             &hunk_line, &eol_str,
+                                             &eof, iterpool));
+      lines_read++;
+      if (! eof && lines_read > hi->fuzz &&
+          lines_read <= hi->hunk->modified_length - hi->fuzz)
+        {
+          if (hunk_line->len >= 1)
+            SVN_ERR(try_stream_write(target->patched, target->patched_path,
+                                     hunk_line->data, hunk_line->len,
+                                     iterpool));
+          if (eol_str)
+            {
+              /* Use the EOL as it was read from the patch file,
+               * unless the target's EOL style is set by svn:eol-style */
+              if (target->eol_style != svn_subst_eol_style_none)
+                eol_str = target->eol_str;
+
+              SVN_ERR(try_stream_write(target->patched, target->patched_path,
+                                       eol_str, strlen(eol_str), iterpool));
+            }
+        }
+    }
+  while (! eof);
+  svn_pool_destroy(iterpool);
 
   return SVN_NO_ERROR;
 }
 
 /* Use client context CTX to send a suitable notification for a patch TARGET.
- * Send WC_PATH as the working copy path in notifications.
  * Use POOL for temporary allocations. */
 static svn_error_t *
-maybe_send_patch_target_notification(const patch_target_t *target,
-                                     const char *wc_path,
-                                     const svn_client_ctx_t *ctx,
-                                     apr_pool_t *pool)
+send_patch_notification(const patch_target_t *target,
+                        const svn_client_ctx_t *ctx,
+                        apr_pool_t *pool)
 {
   svn_wc_notify_t *notify;
   svn_wc_notify_action_t action;
-  const char *path;
 
   if (! ctx->notify_func2)
     return SVN_NO_ERROR;
@@ -853,24 +1020,21 @@ maybe_send_patch_target_notification(con
   if (target->skipped)
     action = svn_wc_notify_skip;
   else if (target->deleted)
-    action = svn_wc_notify_update_delete;
+    action = svn_wc_notify_delete;
   else if (target->added)
-    action = svn_wc_notify_update_add;
-  else
-    action = svn_wc_notify_update_update;
-
-  /* Figure out which path to report for the patch target. */
-  if (! target->skipped)
-    path = svn_dirent_join(wc_path, target->wc_path, pool);
+    action = svn_wc_notify_add;
   else
-    path = target->wc_path;
+    action = svn_wc_notify_patch;
 
-  notify = svn_wc_create_notify(path, action, pool);
+  notify = svn_wc_create_notify(target->abs_path ? target->abs_path
+                                                 : target->rel_path,
+                                action, pool);
   notify->kind = svn_node_file;
 
   if (action == svn_wc_notify_skip)
     {
-      if (target->kind == svn_node_none)
+      if (target->parent_dir_exists &&
+          (target->kind == svn_node_none || target->kind == svn_node_unknown))
         notify->content_state = svn_wc_notify_state_missing;
       else if (target->kind == svn_node_dir)
         notify->content_state = svn_wc_notify_state_obstructed;
@@ -883,118 +1047,164 @@ maybe_send_patch_target_notification(con
         notify->content_state = svn_wc_notify_state_conflicted;
       else if (target->local_mods)
         notify->content_state = svn_wc_notify_state_merged;
-      else if (target->modified)
-        notify->content_state = svn_wc_notify_state_changed;
       else
-        notify->content_state = svn_wc_notify_state_unchanged;
+        notify->content_state = svn_wc_notify_state_changed;
     }
 
   (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
 
+  if (action == svn_wc_notify_patch)
+    {
+      int i;
+      apr_pool_t *iterpool;
+
+      iterpool = svn_pool_create(pool);
+      for (i = 0; i < target->hunks->nelts; i++)
+        {
+          hunk_info_t *hi;
+
+          svn_pool_clear(iterpool);
+
+          hi = APR_ARRAY_IDX(target->hunks, i, hunk_info_t *);
+
+          if (hi->rejected)
+            action = svn_wc_notify_patch_rejected_hunk;
+          else
+            action = svn_wc_notify_patch_applied_hunk;
+
+          notify = svn_wc_create_notify(target->abs_path ? target->abs_path
+                                                         : target->rel_path,
+                                        action, pool);
+          notify->hunk_original_start = hi->hunk->original_start;
+          notify->hunk_original_length = hi->hunk->original_length;
+          notify->hunk_modified_start = hi->hunk->modified_start;
+          notify->hunk_modified_length = hi->hunk->modified_length;
+          notify->hunk_matched_line = hi->matched_line;
+          notify->hunk_fuzz = hi->fuzz;
+
+          (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+        }
+      svn_pool_destroy(iterpool);
+    }
+
   return SVN_NO_ERROR;
 }
 
-/* Apply a PATCH to a working copy at WC_PATH.
- * Use client context CTX to send notifiations and retrieve WC_CTX.
+/* Apply a PATCH to a working copy at ABS_WC_PATH and put the result
+ * into temporary files, to be installed in the working copy later.
+ * Return information about the patch target in *PATCH_TARGET, allocated
+ * in RESULT_POOL. Use WC_CTX as the working copy context.
  * STRIP_COUNT specifies the number of leading path components
  * which should be stripped from target paths in the patch.
- * Do all allocations in POOL. */
+ * If a target does not match a glob in INCLUDE_PATTERNS, mark it as filtered.
+ * If a target matches a glob in EXCLUDE_PATTERNS, mark it as filtered.
+ * If PATCHED_TEMPFILES or REJECT_TEMPFILES are not NULL, add the path
+ * to temporary patched/reject files to them, keyed by the target's path
+ * as parsed from the patch file (after canonicalization).
+ * Do temporary allocations in SCRATCH_POOL. */
 static svn_error_t *
-apply_one_patch(svn_patch_t *patch, const char *wc_path,
-                svn_boolean_t dry_run, const svn_client_ctx_t *ctx,
-                int strip_count, apr_pool_t *pool)
+apply_one_patch(patch_target_t **patch_target, svn_patch_t *patch,
+                const char *abs_wc_path, svn_wc_context_t *wc_ctx,
+                int strip_count,
+                const apr_array_header_t *include_patterns,
+                const apr_array_header_t *exclude_patterns,
+                apr_hash_t *patched_tempfiles,
+                apr_hash_t *reject_tempfiles,
+                apr_pool_t *result_pool, apr_pool_t *scratch_pool)
 {
   patch_target_t *target;
+  apr_pool_t *iterpool;
+  int i;
+  static const int MAX_FUZZ = 2;
 
-  SVN_ERR(init_patch_target(&target, patch, wc_path, ctx, strip_count,
-                            pool, pool));
-  if (target == NULL)
-    /* Can't apply the patch. */
-    return SVN_NO_ERROR;
+  SVN_ERR(init_patch_target(&target, patch, abs_wc_path, wc_ctx, strip_count,
+                            include_patterns, exclude_patterns,
+                            patched_tempfiles, reject_tempfiles,
+                            result_pool, scratch_pool));
 
-  if (! target->skipped)
+  if (target->skipped || target->filtered)
     {
-      apr_pool_t *iterpool;
-      apr_finfo_t working_file;
-      apr_finfo_t patched_file;
-      int i;
+      *patch_target = target;
+      return SVN_NO_ERROR;
+    }
 
-      iterpool = svn_pool_create(pool);
-      /* Match hunks. */
-      for (i = 0; i < patch->hunks->nelts; i++)
-        {
-          svn_hunk_t *hunk;
-          hunk_info_t *hi;
+  iterpool = svn_pool_create(scratch_pool);
+  /* Match hunks. */
+  for (i = 0; i < patch->hunks->nelts; i++)
+    {
+      svn_hunk_t *hunk;
+      hunk_info_t *hi;
+      int fuzz = 0;
 
-          svn_pool_clear(iterpool);
+      svn_pool_clear(iterpool);
 
-          hunk = APR_ARRAY_IDX(patch->hunks, i, svn_hunk_t *);
+      hunk = APR_ARRAY_IDX(patch->hunks, i, svn_hunk_t *);
 
-          /* Determine the line the hunk should be applied at. */
-          SVN_ERR(get_hunk_info(&hi, target, hunk, pool, iterpool));
-          if (hi->matched_line == 0)
-            {
-              /* The hunk does not apply, reject it. */
-              SVN_ERR(reject_hunk(target, hunk, iterpool));
-            }
-          else
-            APR_ARRAY_PUSH(target->matched_hunks, hunk_info_t *) = hi;
+      /* 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, hunk, fuzz,
+                                result_pool, iterpool));
+          fuzz++;
         }
+      while (hi->rejected && fuzz <= MAX_FUZZ);
 
-      /* Apply hunks. */
-      for (i = 0; i < target->matched_hunks->nelts; i++)
-        {
-          const hunk_info_t *hi;
+      APR_ARRAY_PUSH(target->hunks, hunk_info_t *) = hi;
+    }
 
-          svn_pool_clear(iterpool);
+  /* Apply or reject hunks. */
+  for (i = 0; i < target->hunks->nelts; i++)
+    {
+      hunk_info_t *hi;
 
-          hi = APR_ARRAY_IDX(target->matched_hunks, i, const hunk_info_t *);
-          SVN_ERR(apply_one_hunk(hi, target, iterpool));
-        }
-      svn_pool_destroy(iterpool);
+      svn_pool_clear(iterpool);
 
-      if (target->kind == svn_node_file)
-        {
-          /* Copy any remaining lines to target. */
-          SVN_ERR(copy_lines_to_target(target, 0, pool));
-          if (! target->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. Cancel any modifications to the target file and
-               * report is as skipped. */
-              target->modified = FALSE;
-              target->skipped = TRUE;
-            }
+      hi = APR_ARRAY_IDX(target->hunks, i, hunk_info_t *);
+      if (hi->rejected)
+        SVN_ERR(reject_hunk(target, hi, iterpool));
+      else
+        SVN_ERR(apply_hunk(target, hi, iterpool));
+    }
+  svn_pool_destroy(iterpool);
 
-          /* Closing this stream will also close the underlying file. */
-          SVN_ERR(svn_stream_close(target->stream));
+  if (target->kind == svn_node_file)
+    {
+      /* Copy any remaining lines to target. */
+      SVN_ERR(copy_lines_to_target(target, 0, scratch_pool));
+      if (! target->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;
         }
+    }
 
-      /* Close the patched and reject streams so that their content is
-       * flushed to disk. This will also close the raw streams. */
-      SVN_ERR(svn_stream_close(target->patched));
-      SVN_ERR(svn_stream_close(target->reject));
+  /* Close the streams of the target so that their content is
+   * flushed to disk. This will also close underlying streams. */
+  if (target->kind == svn_node_file)
+    SVN_ERR(svn_stream_close(target->stream));
+  SVN_ERR(svn_stream_close(target->patched));
+  SVN_ERR(svn_stream_close(target->reject));
+
+  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 add or delete
-       * the patched file. */
-      SVN_ERR(svn_io_stat(&patched_file, target->patched_path, APR_FINFO_SIZE,
-                          pool));
+       * 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, scratch_pool));
       if (target->kind == svn_node_file)
-        SVN_ERR(svn_io_stat(&working_file, target->abs_path, APR_FINFO_SIZE,
-                            pool));
+        SVN_ERR(svn_io_stat(&working_file, target->abs_path,
+                            APR_FINFO_SIZE, scratch_pool));
       else
         working_file.size = 0;
 
-      if (patched_file.size >= 0 && working_file.size == 0)
-        {
-          /* If the target did not exist we've just added it.
-           * If it did exist the target was empty before patching,
-           * and maybe it is still empty now. */
-          target->added = (target->kind == svn_node_none);
-        }
-      else if (patched_file.size == 0 && 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
@@ -1002,99 +1212,262 @@ apply_one_patch(svn_patch_t *patch, cons
            * 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->kind != svn_node_none);
+          target->deleted = (target->kind == svn_node_file);
         }
+      else if (working_file.size == 0 && patched_file.size == 0)
+        {
+          /* The target was empty or non-existent to begin with
+           * and nothing has changed by patching.
+           * Report this as skipped if it didn't exist. */
+          if (target->kind != svn_node_file)
+            target->skipped = TRUE;
+        }
+      else if (target->kind != svn_node_file && patched_file.size > 0)
+        {
+          /* The patch has created a file. */
+          target->added = TRUE;
+        }
+    }
 
-      if (target->deleted)
+  *patch_target = target;
+  return SVN_NO_ERROR;
+}
+
+/* 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.
+ * 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_pool_t *pool)
+{
+  if (target->deleted)
+    {
+      if (! dry_run)
         {
-          if (! dry_run)
-            {
-              /* Schedule the target for deletion.  Suppress
-               * notification, we'll do it manually in a minute. */
-              SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->abs_path,
-                                     FALSE /* keep_local */, FALSE,
-                                     ctx->cancel_func, ctx->cancel_baton,
-                                     NULL, NULL, pool));
-            }
+          /* Schedule the target for deletion.  Suppress
+           * notification, we'll do it manually in a minute.
+           * Also suppress cancellation. */
+          SVN_ERR(svn_wc_delete4(ctx->wc_ctx, target->abs_path,
+                                 FALSE /* keep_local */, FALSE,
+                                 NULL, NULL, NULL, NULL, pool));
         }
-      else
+    }
+  else
+    {
+      /* If the target's parent directory does not yet exist
+       * we need to create it before we can copy the patched
+       * result in place. */
+      if (target->added && ! target->parent_dir_exists)
         {
-          if (working_file.size == 0 && patched_file.size == 0)
+          const char *abs_path;
+          apr_array_header_t *components;
+          int present_components;
+          int i;
+          apr_pool_t *iterpool;
+
+          /* Check if we can safely create the target's parent. */
+          abs_path = apr_pstrdup(pool, abs_wc_path);
+          components = svn_path_decompose(target->rel_path, pool);
+          present_components = 0;
+          iterpool = svn_pool_create(pool);
+          for (i = 0; i < components->nelts - 1; i++)
             {
-              /* The target was empty or non-existent to begin with
-               * and nothing has changed by patching.
-               * Report this as skipped if it didn't exist. */
-              target->added = FALSE;
-              if (target->kind == svn_node_none)
-                target->skipped = TRUE;
+              const char *component;
+              svn_node_kind_t kind;
+
+              svn_pool_clear(iterpool);
+
+              component = APR_ARRAY_IDX(components, i,
+                                        const char *);
+              abs_path = svn_dirent_join(abs_path, component, pool);
+
+              SVN_ERR(svn_wc__node_get_kind(&kind, ctx->wc_ctx,
+                                            abs_path, TRUE,
+                                            iterpool));
+              if (kind == svn_node_file)
+                {
+                  /* Obstructed. */
+                  target->skipped = TRUE;
+                  break;
+                }
+              else if (kind == svn_node_dir)
+                {
+                  /* ### wc-ng should eventually be able to replace
+                   * directories in-place, so this schedule conflict
+                   * check will go away. We could then also make the
+                   * svn_wc__node_get_kind() call above ignore hidden
+                   * nodes.*/
+                  svn_boolean_t is_deleted;
+
+                  SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted,
+                                                         ctx->wc_ctx,
+                                                         abs_path,
+                                                         iterpool));
+                  if (is_deleted)
+                    {
+                      target->skipped = TRUE;
+                      break;
+                    }
+
+                  present_components++;
+                }
+              else
+                {
+                  /* The WC_DB doesn't know much about this node.
+                   * Check what's on disk. */
+                  svn_node_kind_t disk_kind;
+
+                  SVN_ERR(svn_io_check_path(abs_path, &disk_kind, iterpool));
+                  if (disk_kind != svn_node_none)
+                    {
+                      /* An unversioned item is in the way. */
+                      target->skipped = TRUE;
+                      break;
+                    }
+                }
             }
-          else
-            {
-              target->modified = TRUE;
 
-              if (! dry_run)
+          if (! target->skipped)
+            {
+              abs_path = abs_wc_path;
+              for (i = present_components; i < components->nelts - 1; i++)
                 {
-                  /* Copy the patched file on top of the target file. */
-                  SVN_ERR(svn_io_copy_file(target->patched_path,
-                                           target->abs_path, FALSE, pool));
-                  if (target->added)
+                  const char *component;
+
+                  svn_pool_clear(iterpool);
+
+                  component = APR_ARRAY_IDX(components, i,
+                                            const char *);
+                  abs_path = svn_dirent_join(abs_path, component,
+                                             pool);
+                  if (dry_run)
                     {
-                      /* The target file didn't exist previously,
-                       * so add it to version control.
-                       * Suppress notification, we'll do that later.
-                       * Also suppress cancellation. */
-                      SVN_ERR(svn_wc_add4(ctx->wc_ctx, target->abs_path,
+                      if (ctx->notify_func2)
+                        {
+                          /* Just do notification. */
+                          svn_wc_notify_t *notify;
+                          notify = svn_wc_create_notify(abs_path,
+                                                        svn_wc_notify_add,
+                                                        iterpool);
+                          notify->kind = svn_node_dir;
+                          ctx->notify_func2(ctx->notify_baton2, notify,
+                                            iterpool);
+                        }
+                    }
+                  else
+                    {
+                      /* Create the missing component and add it
+                       * to version control. Suppress cancellation. */
+                      SVN_ERR(svn_io_dir_make(abs_path, APR_OS_DEFAULT,
+                                              iterpool));
+                      SVN_ERR(svn_wc_add4(ctx->wc_ctx, abs_path,
                                           svn_depth_infinity,
                                           NULL, SVN_INVALID_REVNUM,
-                                          NULL, NULL, NULL, NULL, pool));
+                                          NULL, NULL,
+                                          ctx->notify_func2,
+                                          ctx->notify_baton2,
+                                          iterpool));
                     }
-
-                  /* Restore the target's executable bit if necessary. */
-                  SVN_ERR(svn_io_set_file_executable(target->abs_path,
-                                                     target->executable,
-                                                     FALSE, pool));
                 }
             }
+          svn_pool_destroy(iterpool);
         }
 
-      /* Write out rejected hunks, if any. */
-      if (target->had_rejects)
+      if (! dry_run && ! target->skipped)
         {
-          SVN_ERR(svn_io_copy_file(target->reject_path,
-                                   apr_psprintf(pool, "%s.svnpatch.rej",
-                                                target->abs_path),
-                                   FALSE, pool));
-          /* ### TODO mark file as conflicted. */
-        }
+          /* Copy the patched file on top of the target file. */
+          SVN_ERR(svn_io_copy_file(target->patched_path,
+                                   target->abs_path, FALSE, pool));
+          if (target->added)
+            {
+              /* The target file didn't exist previously,
+               * so add it to version control.
+               * Suppress notification, we'll do that later.
+               * Also suppress cancellation. */
+              SVN_ERR(svn_wc_add4(ctx->wc_ctx, target->abs_path,
+                                  svn_depth_infinity,
+                                  NULL, SVN_INVALID_REVNUM,
+                                  NULL, NULL, NULL, NULL, pool));
+            }
 
+          /* Restore the target's executable bit if necessary. */
+          SVN_ERR(svn_io_set_file_executable(target->abs_path,
+                                             target->executable,
+                                             FALSE, pool));
+        }
     }
 
-  SVN_ERR(maybe_send_patch_target_notification(target, wc_path, ctx, pool));
+  /* Write out rejected hunks, if any. */
+  if (! dry_run && ! target->skipped && target->had_rejects)
+    {
+      SVN_ERR(svn_io_copy_file(target->reject_path,
+                               apr_psprintf(pool, "%s.svnpatch.rej",
+                                            target->abs_path),
+                               FALSE, pool));
+      /* ### TODO mark file as conflicted. */
+    }
 
   return SVN_NO_ERROR;
 }
 
-/* Apply all diffs in the patch file at PATCH_PATH to the working copy
- * at WC_PATH.
- * TODO ### DRY_RUN ...
- * TODO ### STRIP_COUNT ...
- * Use client context CTX to send notifiations.
- * Do all allocations in POOL.  */
+/* Baton for apply_patches(). */
+typedef struct {
+  /* The path to the patch file. */
+  const char *abs_patch_path;
+
+  /* The abspath to the working copy the patch should be applied to. */
+  const char *abs_wc_path;
+
+  /* Indicates whether we're doing a dry run. */
+  svn_boolean_t dry_run;
+
+  /* Number of leading components to strip from patch target paths. */
+  int strip_count;
+
+  /* Whether to apply the patch in reverse. */
+  svn_boolean_t reverse;
+
+  /* Files not matching any of these patterns won't be patched. */
+  const apr_array_header_t *include_patterns;
+
+  /* Files matching any of these patterns won't be patched. */
+  const apr_array_header_t *exclude_patterns;
+
+  /* Mapping patch target path -> path to tempfile with patched result. */
+  apr_hash_t *patched_tempfiles;
+
+  /* Mapping patch target path -> path to tempfile with rejected hunks. */
+  apr_hash_t *reject_tempfiles;
+
+
+  /* The client context. */
+  svn_client_ctx_t *ctx;
+} apply_patches_baton_t;
+
+/* Callback for use with svn_wc__call_with_write_lock().
+ * This function is the main entry point into the patch code. */
 static svn_error_t *
-apply_textdiffs(const char *patch_path, const char *wc_path,
-                svn_boolean_t dry_run, const svn_client_ctx_t *ctx,
-                int strip_count, apr_pool_t *pool)
+apply_patches(void *baton,
+              apr_pool_t *result_pool,
+              apr_pool_t *scratch_pool)
 {
   svn_patch_t *patch;
   apr_pool_t *iterpool;
   const char *patch_eol_str;
   apr_file_t *patch_file;
+  apr_array_header_t *targets;
+  int i;
+  apply_patches_baton_t *btn;
+
+  btn = (apply_patches_baton_t *)baton;
 
   /* Try to open the patch file. */
-  SVN_ERR(svn_io_file_open(&patch_file, patch_path,
-                           APR_READ | APR_BINARY, 0, pool));
+  SVN_ERR(svn_io_file_open(&patch_file, btn->abs_patch_path,
+                           APR_READ | APR_BINARY, 0, scratch_pool));
 
-  SVN_ERR(svn_eol__detect_file_eol(&patch_eol_str, patch_file, pool));
+  SVN_ERR(svn_eol__detect_file_eol(&patch_eol_str, patch_file, scratch_pool));
   if (patch_eol_str == NULL)
     {
       /* If we can't figure out the EOL scheme, just assume native.
@@ -1104,58 +1477,105 @@ apply_textdiffs(const char *patch_path, 
     }
 
   /* Apply patches. */
-  iterpool = svn_pool_create(pool);
+  targets = apr_array_make(scratch_pool, 0, sizeof(patch_target_t *));
+  iterpool = svn_pool_create(scratch_pool);
   do
     {
       svn_pool_clear(iterpool);
 
-      if (ctx->cancel_func)
-        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
+      if (btn->ctx->cancel_func)
+        SVN_ERR(btn->ctx->cancel_func(btn->ctx->cancel_baton));
 
-      SVN_ERR(svn_diff__parse_next_patch(&patch, patch_file, iterpool,
-                                         iterpool));
+      SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
+                                        btn->reverse, scratch_pool, iterpool));
       if (patch)
         {
-          SVN_ERR(apply_one_patch(patch, wc_path, dry_run, ctx, strip_count,
-                                  iterpool));
-          SVN_ERR(svn_diff__close_patch(patch));
+          patch_target_t *target;
+
+          SVN_ERR(apply_one_patch(&target, patch, btn->abs_wc_path,
+                                  btn->ctx->wc_ctx, btn->strip_count,
+                                  btn->include_patterns, btn->exclude_patterns,
+                                  btn->patched_tempfiles, btn->reject_tempfiles,
+                                  result_pool, iterpool));
+          if (target->filtered)
+            SVN_ERR(svn_diff_close_patch(patch));
+          else
+            APR_ARRAY_PUSH(targets, patch_target_t *) = target;
         }
     }
   while (patch);
+
+  /* Install patched targets into the working copy. */
+  for (i = 0; i < targets->nelts; i++)
+    {
+      patch_target_t *target;
+
+      svn_pool_clear(iterpool);
+
+      if (btn->ctx->cancel_func)
+        SVN_ERR(btn->ctx->cancel_func(btn->ctx->cancel_baton));
+
+      target = APR_ARRAY_IDX(targets, i, patch_target_t *);
+      if (! target->skipped)
+        SVN_ERR(install_patched_target(target, btn->abs_wc_path,
+                                       btn->ctx, btn->dry_run, iterpool));
+      SVN_ERR(send_patch_notification(target, btn->ctx, iterpool));
+      SVN_ERR(svn_diff_close_patch(target->patch));
+    }
+
+  SVN_ERR(svn_io_file_close(patch_file, iterpool));
+
   svn_pool_destroy(iterpool);
 
   return SVN_NO_ERROR;
 }
 
 svn_error_t *
-svn_client_patch(const char *patch_path,
-                 const char *target,
+svn_client_patch(const char *abs_patch_path,
+                 const char *local_abspath,
                  svn_boolean_t dry_run,
                  int strip_count,
+                 svn_boolean_t reverse,
+                 const apr_array_header_t *include_patterns,
+                 const apr_array_header_t *exclude_patterns,
+                 apr_hash_t **patched_tempfiles,
+                 apr_hash_t **reject_tempfiles,
                  svn_client_ctx_t *ctx,
-                 apr_pool_t *pool)
+                 apr_pool_t *result_pool,
+                 apr_pool_t *scratch_pool)
 {
-  svn_wc_adm_access_t *adm_access;
-  const char *abs_target;
+  apply_patches_baton_t baton;
 
   if (strip_count < 0)
     return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
                             _("strip count must be positive"));
 
-  /* svn_wc_add4() and svn_wc_delete4() internally obtain an adm_access
-   * write lock, so lock the entire working copy, the old way, for now.
-   * <Bert> You need some kind of write lock to make sure another
-   *        concurrent client can't also update the WC via its entries cache
-   *        at the same time.. And currently access batons are the only write
-   *        locks we have */
-  SVN_ERR(svn_dirent_get_absolute(&abs_target, target, pool));
-  SVN_ERR(svn_wc__adm_open_in_context(&adm_access, ctx->wc_ctx, abs_target,
-                                      TRUE, -1, ctx->cancel_func,
-                                      ctx->cancel_baton, pool));
-
-  SVN_ERR(apply_textdiffs(patch_path, target, dry_run, ctx, strip_count, pool));
+  baton.abs_patch_path = abs_patch_path;
+  baton.abs_wc_path = local_abspath;
+  baton.dry_run = dry_run;
+  baton.ctx = ctx;
+  baton.strip_count = strip_count;
+  baton.reverse = reverse;
+  baton.include_patterns = include_patterns;
+  baton.exclude_patterns = exclude_patterns;
+  if (patched_tempfiles)
+    {
+      (*patched_tempfiles) = apr_hash_make(result_pool);
+      baton.patched_tempfiles = (*patched_tempfiles);
+    }
+  else
+    baton.patched_tempfiles = NULL;
+  if (reject_tempfiles)
+    {
+      (*reject_tempfiles) = apr_hash_make(result_pool);
+      baton.reject_tempfiles = (*reject_tempfiles);
+    }
+  else
+    baton.reject_tempfiles = NULL;
 
-  SVN_ERR(svn_wc_adm_close2(adm_access, pool));
+  SVN_ERR(svn_wc__call_with_write_lock(apply_patches, &baton,
+                                       ctx->wc_ctx, local_abspath,
+                                       result_pool, scratch_pool));
 
   return SVN_NO_ERROR;
 }

Modified: subversion/branches/ignore-mergeinfo/subversion/libsvn_client/prop_commands.c
URL: http://svn.apache.org/viewvc/subversion/branches/ignore-mergeinfo/subversion/libsvn_client/prop_commands.c?rev=984153&r1=984152&r2=984153&view=diff
==============================================================================
--- subversion/branches/ignore-mergeinfo/subversion/libsvn_client/prop_commands.c (original)
+++ subversion/branches/ignore-mergeinfo/subversion/libsvn_client/prop_commands.c Tue Aug 10 18:06:17 2010
@@ -711,8 +711,8 @@ remote_propget(apr_hash_t *props,
            hi;
            hi = apr_hash_next(hi))
         {
-          const char *this_name = svn_apr_hash_index_key(hi);
-          svn_dirent_t *this_ent = svn_apr_hash_index_val(hi);
+          const char *this_name = svn__apr_hash_index_key(hi);
+          svn_dirent_t *this_ent = svn__apr_hash_index_val(hi);
           const char *new_target_relative;
           svn_depth_t depth_below_here = depth;
 
@@ -1008,9 +1008,9 @@ remote_proplist(const char *target_prefi
        hi;
        hi = apr_hash_next(hi))
     {
-      const char *name = svn_apr_hash_index_key(hi);
-      apr_ssize_t klen = svn_apr_hash_index_klen(hi);
-      svn_string_t *value = svn_apr_hash_index_val(hi);
+      const char *name = svn__apr_hash_index_key(hi);
+      apr_ssize_t klen = svn__apr_hash_index_klen(hi);
+      svn_string_t *value = svn__apr_hash_index_val(hi);
       svn_prop_kind_t prop_kind;
 
       prop_kind = svn_property_kind(NULL, name);
@@ -1036,8 +1036,8 @@ remote_proplist(const char *target_prefi
            hi;
            hi = apr_hash_next(hi))
         {
-          const char *this_name = svn_apr_hash_index_key(hi);
-          svn_dirent_t *this_ent = svn_apr_hash_index_val(hi);
+          const char *this_name = svn__apr_hash_index_key(hi);
+          svn_dirent_t *this_ent = svn__apr_hash_index_val(hi);
           const char *new_target_relative;
 
           svn_pool_clear(subpool);

Modified: subversion/branches/ignore-mergeinfo/subversion/libsvn_client/ra.c
URL: http://svn.apache.org/viewvc/subversion/branches/ignore-mergeinfo/subversion/libsvn_client/ra.c?rev=984153&r1=984152&r2=984153&view=diff
==============================================================================
--- subversion/branches/ignore-mergeinfo/subversion/libsvn_client/ra.c (original)
+++ subversion/branches/ignore-mergeinfo/subversion/libsvn_client/ra.c Tue Aug 10 18:06:17 2010
@@ -57,7 +57,7 @@ typedef struct
 
   /* An array of svn_client_commit_item3_t * structures, present only
      during working copy commits. */
-  apr_array_header_t *commit_items;
+  const apr_array_header_t *commit_items;
 
   /* A client context. */
   svn_client_ctx_t *ctx;
@@ -290,7 +290,7 @@ svn_error_t *
 svn_client__open_ra_session_internal(svn_ra_session_t **ra_session,
                                      const char *base_url,
                                      const char *base_dir,
-                                     apr_array_header_t *commit_items,
+                                     const apr_array_header_t *commit_items,
                                      svn_boolean_t use_admin,
                                      svn_boolean_t read_only_wc,
                                      svn_client_ctx_t *ctx,
@@ -322,11 +322,22 @@ svn_client__open_ra_session_internal(svn
   if (base_dir)
     {
       const char *base_dir_abspath;
+      svn_error_t *err;
 
       SVN_ERR(svn_dirent_get_absolute(&base_dir_abspath, base_dir, pool));
-      SVN_ERR(svn_wc__node_get_repos_info(NULL, &uuid, ctx->wc_ctx,
-                                          base_dir_abspath, FALSE,
-                                          pool, pool));
+      err = svn_wc__node_get_repos_info(NULL, &uuid, ctx->wc_ctx,
+                                        base_dir_abspath, FALSE,
+                                        pool, pool);
+
+      if (err && (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY
+                  || err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND
+                  || err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED))
+        {
+          svn_error_clear(err);
+          uuid = NULL;
+        }
+      else
+        SVN_ERR(err);
     }
 
   return svn_error_return(svn_ra_open3(ra_session, base_url, uuid, cbtable, cb,
@@ -384,6 +395,17 @@ svn_client_uuid_from_path2(const char **
 
 
 
+/* Convert a path or URL for display: if it is a local path, convert it to
+ * the local path style; if it is a URL, return it unchanged. */
+static const char *
+path_or_url_local_style(const char *path_or_url,
+                        apr_pool_t *pool)
+{
+  if (svn_path_is_url(path_or_url))
+    return path_or_url;
+  return svn_dirent_local_style(path_or_url, pool);
+}
+
 svn_error_t *
 svn_client__ra_session_from_path(svn_ra_session_t **ra_session_p,
                                  svn_revnum_t *rev_p,
@@ -664,7 +686,7 @@ svn_client__repos_locations(const char *
     return svn_error_createf
       (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
        _("Unable to find repository location for '%s' in revision %ld"),
-       svn_dirent_local_style(path, pool), start_revnum);
+       path_or_url_local_style(path, pool), start_revnum);
 
   end_path = apr_hash_get(rev_locs, &end_revnum, sizeof(svn_revnum_t));
   if (! end_path)
@@ -672,7 +694,7 @@ svn_client__repos_locations(const char *
       (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
        _("The location for '%s' for revision %ld does not exist in the "
          "repository or refers to an unrelated object"),
-       svn_dirent_local_style(path, pool), end_revnum);
+       path_or_url_local_style(path, pool), end_revnum);
 
   /* Repository paths might be absolute, but we want to treat them as
      relative.
@@ -732,9 +754,9 @@ svn_client__get_youngest_common_ancestor
      remembering the youngest matching location. */
   for (hi = apr_hash_first(pool, history1); hi; hi = apr_hash_next(hi))
     {
-      const char *path = svn_apr_hash_index_key(hi);
-      apr_ssize_t path_len = svn_apr_hash_index_klen(hi);
-      apr_array_header_t *ranges1 = svn_apr_hash_index_val(hi);
+      const char *path = svn__apr_hash_index_key(hi);
+      apr_ssize_t path_len = svn__apr_hash_index_klen(hi);
+      apr_array_header_t *ranges1 = svn__apr_hash_index_val(hi);
       apr_array_header_t *ranges2, *common;
 
       ranges2 = apr_hash_get(history2, path, path_len);

Modified: subversion/branches/ignore-mergeinfo/subversion/libsvn_client/repos_diff.c
URL: http://svn.apache.org/viewvc/subversion/branches/ignore-mergeinfo/subversion/libsvn_client/repos_diff.c?rev=984153&r1=984152&r2=984153&view=diff
==============================================================================
--- subversion/branches/ignore-mergeinfo/subversion/libsvn_client/repos_diff.c (original)
+++ subversion/branches/ignore-mergeinfo/subversion/libsvn_client/repos_diff.c Tue Aug 10 18:06:17 2010
@@ -50,8 +50,9 @@ struct edit_baton {
      URL open in RA_SESSION below. */
   const char *target;
 
-  /* ADM_ACCESS is an access baton that includes the TARGET directory */
-  svn_wc_adm_access_t *adm_access;
+  /* A working copy context for TARGET, NULL if this is purely a
+     repository operation. */
+  svn_wc_context_t *wc_ctx;
 
   /* The callback and calback argument that implement the file comparison
      function */
@@ -321,56 +322,65 @@ get_dirprops_from_ra(struct dir_baton *b
 }
 
 
-/* Return in *LOCAL_DIR_ABSPATH the absolute path for the directory PATH by
-   searching the access baton set of ADM_ACCESS.  If ADM_ACCESS is NULL then
-   *LOCAL_DIR_ABSPATH will be NULL.  If LENIENT is TRUE then failure to find
-   an access baton will not return an error but will set *LOCAL_DIR_ABSPATH to
-   NULL instead. */
+/* If WC_CTX is NULL then set *LOCAL_DIR_ABSPATH to NULL otherwise
+   return in *LOCAL_DIR_ABSPATH the absolute path for the directory
+   PATH if PATH is a versioned directory. If PATH is not a versioned
+   directory and LENIENT is FALSE then return an error
+   SVN_ERR_WC_NOT_WORKING_COPY.  If LENIENT is TRUE then no error will
+   be returned but instead *LOCAL_DIR_ABSPATH will be set to NULL.
+
+   This rather odd interface was originally designed around searching
+   an access baton set. */
 static svn_error_t *
 get_dir_abspath(const char **local_dir_abspath,
-                svn_wc_adm_access_t *adm_access,
+                svn_wc_context_t *wc_ctx,
                 const char *path,
                 svn_boolean_t lenient,
                 apr_pool_t *pool)
 {
   *local_dir_abspath = NULL;
 
-  if (adm_access)
+  if (wc_ctx)
     {
-      svn_wc_adm_access_t *path_access;
-      svn_error_t *err = svn_wc_adm_retrieve(&path_access, adm_access, path,
-                                             pool);
+      svn_node_kind_t kind;
+      svn_error_t *err;
+      const char *local_abspath;
+      SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+      err = svn_wc__node_get_kind(&kind, wc_ctx, local_abspath, FALSE, pool);
       if (err)
         {
-          if (! lenient)
+          if (lenient)
+            kind = svn_node_none;
+          else
             return svn_error_return(err);
-          svn_error_clear(err);
         }
-      else if (path_access != NULL)
-        SVN_ERR(svn_dirent_get_absolute(local_dir_abspath,
-                                        svn_wc_adm_access_path(path_access),
-                                        pool));
-
+      svn_error_clear(err);
+      if (kind == svn_node_dir)
+        *local_dir_abspath = local_abspath;
+      else if (!lenient)
+        return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+                                 "'%s' is not a versioned directory",
+                                 svn_dirent_local_style(local_abspath, pool));
     }
 
   return SVN_NO_ERROR;
 }
 
-/* Like get_path_access except the returned access baton, in
-   *PARENT_ACCESS, is for the parent of PATH rather than for PATH
-   itself. */
+/* Like get_path_access except the returned path, in
+   *LOCAL_PARENT_DIR_ABSPATH, is for the parent of PATH rather than
+   for PATH itself.  As for get_path_access WC_CTX may be NULL. */
 static svn_error_t *
 get_parent_dir_abspath(const char **local_parent_dir_abspath,
-                       svn_wc_adm_access_t *adm_access,
+                       svn_wc_context_t *wc_ctx,
                        const char *path,
                        svn_boolean_t lenient,
-                      apr_pool_t *pool)
+                       apr_pool_t *pool)
 {
-  if (! adm_access)
+  if (!wc_ctx)
     *local_parent_dir_abspath = NULL;  /* Avoid messing around with paths */
   else
     {
-      SVN_ERR(get_dir_abspath(local_parent_dir_abspath, adm_access,
+      SVN_ERR(get_dir_abspath(local_parent_dir_abspath, wc_ctx,
                               svn_dirent_dirname(path, pool),
                               lenient, pool));
     }
@@ -450,9 +460,9 @@ delete_entry(const char *path,
 
   /* We need to know if this is a directory or a file */
   SVN_ERR(svn_ra_check_path(eb->ra_session, path, eb->revision, &kind, pool));
-  SVN_ERR(get_dir_abspath(&local_dir_abspath, eb->adm_access, pb->wcpath,
+  SVN_ERR(get_dir_abspath(&local_dir_abspath, eb->wc_ctx, pb->wcpath,
                           TRUE, pool));
-  if ((! eb->adm_access) || local_dir_abspath)
+  if ((! eb->wc_ctx) || local_dir_abspath)
     {
       switch (kind)
         {
@@ -554,7 +564,7 @@ add_directory(const char *path,
     }
 
 
-  SVN_ERR(get_dir_abspath(&local_dir_abspath, eb->adm_access, pb->wcpath, TRUE,
+  SVN_ERR(get_dir_abspath(&local_dir_abspath, eb->wc_ctx, pb->wcpath, TRUE,
                           pool));
 
   SVN_ERR(eb->diff_callbacks->dir_added
@@ -642,7 +652,7 @@ open_directory(const char *path,
 
   SVN_ERR(get_dirprops_from_ra(b, base_revision));
 
-  SVN_ERR(get_dir_abspath(&local_dir_abspath, eb->adm_access, pb->wcpath, TRUE,
+  SVN_ERR(get_dir_abspath(&local_dir_abspath, eb->wc_ctx, pb->wcpath, TRUE,
                           pool));
 
   SVN_ERR(eb->diff_callbacks->dir_opened
@@ -799,10 +809,10 @@ close_file(void *file_baton,
   if (b->skip)
     return SVN_NO_ERROR;
 
-  err = get_parent_dir_abspath(&local_dir_abspath, eb->adm_access,
+  err = get_parent_dir_abspath(&local_dir_abspath, eb->wc_ctx,
                                b->wcpath, eb->dry_run, b->pool);
 
-  if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED)
+  if (err && err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)
     {
       /* ### maybe try to stat the local b->wcpath? */
       /* If the file path doesn't exist, then send a 'skipped' notification. */
@@ -934,10 +944,10 @@ close_directory(void *dir_baton,
   if (eb->dry_run)
     svn_hash__clear(svn_client__dry_run_deletions(eb->diff_cmd_baton), pool);
 
-  err = get_dir_abspath(&local_dir_abspath, eb->adm_access, b->wcpath,
+  err = get_dir_abspath(&local_dir_abspath, eb->wc_ctx, b->wcpath,
                         eb->dry_run, b->pool);
 
-  if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED)
+  if (err && err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)
     {
       /* ### maybe try to stat the local b->wcpath? */
       /* If the path doesn't exist, then send a 'skipped' notification.
@@ -991,8 +1001,8 @@ close_directory(void *dir_baton,
       for (hi = apr_hash_first(pool, eb->deleted_paths); hi;
            hi = apr_hash_next(hi))
         {
-          const char *deleted_path = svn_apr_hash_index_key(hi);
-          deleted_path_notify_t *dpn = svn_apr_hash_index_val(hi);
+          const char *deleted_path = svn__apr_hash_index_key(hi);
+          deleted_path_notify_t *dpn = svn__apr_hash_index_val(hi);
 
           notify = svn_wc_create_notify(deleted_path, dpn->action, pool);
           notify->kind = dpn->kind;
@@ -1161,8 +1171,7 @@ svn_client__get_diff_editor(const char *
   SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, pool));
 
   eb->target = target;
-  SVN_ERR(svn_wc__adm_retrieve_from_context(&(eb->adm_access), wc_ctx,
-                                            target_abspath, pool));
+  eb->wc_ctx = wc_ctx;
   eb->diff_callbacks = diff_callbacks;
   eb->diff_cmd_baton = diff_cmd_baton;
   eb->dry_run = dry_run;