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/09/06 22:02:24 UTC

svn commit: r993141 [8/25] - in /subversion/branches/performance: ./ build/ac-macros/ build/generator/ contrib/server-side/ notes/ notes/wc-ng/ subversion/bindings/javahl/native/ subversion/bindings/javahl/src/org/apache/subversion/javahl/ subversion/b...

Modified: subversion/branches/performance/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/performance/subversion/libsvn_client/merge.c?rev=993141&r1=993140&r2=993141&view=diff
==============================================================================
--- subversion/branches/performance/subversion/libsvn_client/merge.c (original)
+++ subversion/branches/performance/subversion/libsvn_client/merge.c Mon Sep  6 20:02:15 2010
@@ -378,8 +378,9 @@ is_path_conflicted_by_merge(merge_cmd_ba
  *     different kind is expected, or if the disk node cannot be read.
  *   - Return 'missing' if there is no node on disk but one is expected.
  *     Also return 'missing' for absent nodes (not here due to authz).*/
-static svn_wc_notify_state_t
-obstructed_or_missing(const char *local_abspath,
+static svn_error_t *
+obstructed_or_missing(svn_wc_notify_state_t *obstr_state,
+                      const char *local_abspath,
                       const char *local_dir_abspath,
                       const merge_cmd_baton_t *merge_b,
                       apr_pool_t *pool)
@@ -391,20 +392,14 @@ obstructed_or_missing(const char *local_
 
   /* In a dry run, make as if nodes "deleted" by the dry run appear so. */
   if (merge_b->dry_run && dry_run_deleted_p(merge_b, local_abspath))
-    return svn_wc_notify_state_inapplicable;
-
-  /* Since this function returns no svn_error_t, we make all errors look like
-   * no node found in the wc. */
-
-  err = svn_wc_read_kind(&kind_expected, merge_b->ctx->wc_ctx,
-                         local_abspath, FALSE, pool);
-
-  if (err)
     {
-      svn_error_clear(err);
-      kind_expected = svn_node_none;
+      *obstr_state = svn_wc_notify_state_inapplicable;
+      return SVN_NO_ERROR;
     }
 
+  SVN_ERR(svn_wc_read_kind(&kind_expected, merge_b->ctx->wc_ctx,
+                           local_abspath, FALSE, pool));
+
   /* Report absent nodes as 'missing'. First of all, they count as 'hidden',
    * and since we pass show_hidden == FALSE above, they will show up as
    * 'none' here. */
@@ -413,44 +408,68 @@ obstructed_or_missing(const char *local_
       svn_boolean_t is_absent;
       err = svn_wc__node_is_status_absent(&is_absent, merge_b->ctx->wc_ctx,
                                           local_abspath, pool);
-      if (err)
-        svn_error_clear(err);
-      else if (is_absent)
-        return svn_wc_notify_state_missing;
+      if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
+        return svn_error_return(err);
+      else if (!err && is_absent)
+        {
+          *obstr_state = svn_wc_notify_state_missing;
+          return SVN_NO_ERROR;
+        }
+
+      svn_error_clear(err);
     }
 
-  /* If a node is deleted, we will still get its kind from the working copy.
+  SVN_ERR(svn_io_check_path(local_abspath, &kind_on_disk, pool));
+
+  /* If a node is deleted, the node might be gone in the working copy (and
+   * it probably is gone after we switch to single-db
+   *
    * But to compare with disk state, we want to consider deleted nodes as
-   * svn_node_none instead of their original kind.
-   * ### Not necessary with central DB:
-   * Because deleted directories are expected to remain on disk until commit
-   * to keep the metadata available, we only do this for files, not dirs. */
-  if (kind_expected == svn_node_file)
+   * svn_node_none instead of their original kind. */
+
+  if (kind_expected == svn_node_file
+      || kind_expected == svn_node_dir)
     {
       svn_boolean_t is_deleted;
-      err = svn_wc__node_is_status_deleted(&is_deleted,
-                                           merge_b->ctx->wc_ctx,
-                                           local_abspath,
-                                           pool);
-      if (err)
-        svn_error_clear(err);
-      else if (is_deleted)
-        kind_expected = svn_node_none;
-    }
 
-  err = svn_io_check_path(local_abspath, &kind_on_disk, pool);
-  if (err)
-    {
-      svn_error_clear(err);
-      kind_on_disk = svn_node_unknown;
+      SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted,
+                                             merge_b->ctx->wc_ctx,
+                                             local_abspath,
+                                             pool));
+      if (is_deleted)
+        {
+#ifndef SVN_WC__SINGLE_DB
+          /* ### While we are not at single-db: detect missing .svn dirs.
+             ### Once we switch to single db expected kind should be always
+             ### none, just like for files */
+          if (kind_expected == svn_node_dir)
+            {
+              if (kind_on_disk == svn_node_none)
+                {
+                  svn_boolean_t is_obstructed;
+                  
+                  SVN_ERR(svn_wc__node_is_status_obstructed(&is_obstructed,
+                                                            merge_b->ctx->wc_ctx,
+                                                            local_abspath,
+                                                            pool));
+                  if (!is_obstructed)
+                    kind_expected = svn_node_none; 
+                }
+            }
+          else
+#endif
+            kind_expected = svn_node_none;
+        }
     }
 
   if (kind_expected == kind_on_disk)
-    return svn_wc_notify_state_inapplicable;
+    *obstr_state = svn_wc_notify_state_inapplicable;
   else if (kind_on_disk == svn_node_none)
-    return svn_wc_notify_state_missing;
+    *obstr_state = svn_wc_notify_state_missing;
   else
-    return svn_wc_notify_state_obstructed;
+    *obstr_state = svn_wc_notify_state_obstructed;
+
+  return SVN_NO_ERROR;
 }
 
 /* Create *LEFT and *RIGHT conflict versions for conflict victim
@@ -1083,8 +1102,9 @@ merge_props_changed(const char *local_di
   {
     svn_wc_notify_state_t obstr_state;
 
-    obstr_state = obstructed_or_missing(local_abspath, local_dir_abspath,
-                                        merge_b, subpool);
+    SVN_ERR(obstructed_or_missing(&obstr_state,
+                                  local_abspath, local_dir_abspath,
+                                  merge_b, subpool));
     if (obstr_state != svn_wc_notify_state_inapplicable)
       {
         if (state)
@@ -1319,8 +1339,9 @@ merge_file_changed(const char *local_dir
   {
     svn_wc_notify_state_t obstr_state;
 
-    obstr_state = obstructed_or_missing(mine_abspath, local_dir_abspath,
-                                        merge_b, subpool);
+    SVN_ERR(obstructed_or_missing(&obstr_state,
+                                  mine_abspath, local_dir_abspath,
+                                  merge_b, subpool));
     if (obstr_state != svn_wc_notify_state_inapplicable)
       {
         if (content_state)
@@ -1349,7 +1370,9 @@ merge_file_changed(const char *local_dir
     SVN_ERR(svn_wc__node_get_depth(&parent_depth, merge_b->ctx->wc_ctx,
                                    svn_dirent_dirname(mine_abspath, subpool),
                                    subpool));
-    if (wc_kind == svn_node_none && parent_depth < svn_depth_files)
+    if (wc_kind == svn_node_none
+        && parent_depth < svn_depth_files
+        && parent_depth != svn_depth_unknown)
       {
         if (content_state)
           *content_state = svn_wc_notify_state_missing;
@@ -1635,8 +1658,9 @@ merge_file_added(const char *local_dir_a
   {
     svn_wc_notify_state_t obstr_state;
 
-    obstr_state = obstructed_or_missing(mine_abspath, local_dir_abspath,
-                                        merge_b, subpool);
+    SVN_ERR(obstructed_or_missing(&obstr_state,
+                                  mine_abspath, local_dir_abspath,
+                                  merge_b, subpool));
     if (obstr_state != svn_wc_notify_state_inapplicable)
       {
         if (content_state)
@@ -1921,8 +1945,9 @@ merge_file_deleted(const char *local_dir
   {
     svn_wc_notify_state_t obstr_state;
 
-    obstr_state = obstructed_or_missing(mine_abspath, local_dir_abspath,
-                                        merge_b, subpool);
+    SVN_ERR(obstructed_or_missing(&obstr_state,
+                                  mine_abspath, local_dir_abspath,
+                                  merge_b, subpool));
     if (obstr_state != svn_wc_notify_state_inapplicable)
       {
         if (state)
@@ -2109,8 +2134,9 @@ merge_dir_added(const char *local_dir_ab
   {
     svn_wc_notify_state_t obstr_state;
 
-    obstr_state = obstructed_or_missing(local_abspath, local_dir_abspath,
-                                        merge_b, subpool);
+    SVN_ERR(obstructed_or_missing(&obstr_state,
+                                  local_abspath, local_dir_abspath,
+                                  merge_b, subpool));
 
     /* In this case of adding a directory, we have an exception to the usual
      * "skip if it's inconsistent" rule. If the directory exists on disk
@@ -2138,7 +2164,7 @@ merge_dir_added(const char *local_dir_ab
         merge_b->added_path = apr_pstrdup(merge_b->pool, local_abspath);
       else
         {
-          SVN_ERR(svn_io_make_dir_recursively(local_abspath, subpool));
+          SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, subpool));
           SVN_ERR(svn_wc_add4(merge_b->ctx->wc_ctx, local_abspath,
                               svn_depth_infinity,
                               copyfrom_url, copyfrom_rev,
@@ -2298,8 +2324,9 @@ merge_dir_deleted(const char *local_dir_
   {
     svn_wc_notify_state_t obstr_state;
 
-    obstr_state = obstructed_or_missing(local_abspath, local_dir_abspath,
-                                        merge_b, subpool);
+    SVN_ERR(obstructed_or_missing(&obstr_state,
+                                  local_abspath, local_dir_abspath,
+                                  merge_b, subpool));
     if (obstr_state != svn_wc_notify_state_inapplicable)
       {
         if (state)
@@ -2430,8 +2457,9 @@ merge_dir_opened(const char *local_dir_a
     }
 
   /* Check for an obstructed or missing node on disk. */
-  obstr_state = obstructed_or_missing(path, local_dir_abspath, merge_b,
-                                      subpool);
+  SVN_ERR(obstructed_or_missing(&obstr_state,
+                                path, local_dir_abspath,
+                                merge_b, subpool));
   if (obstr_state != svn_wc_notify_state_inapplicable)
     {
       if (skip_children)
@@ -3341,6 +3369,7 @@ get_full_mergeinfo(svn_mergeinfo_t *reco
       svn_revnum_t target_rev;
       svn_opt_revision_t peg_revision;
       apr_pool_t *sesspool = NULL;
+      svn_error_t *err;
 
       /* Assert that we have sane input. */
       SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start)
@@ -3348,9 +3377,26 @@ get_full_mergeinfo(svn_mergeinfo_t *reco
                  && (start > end));
 
       peg_revision.kind = svn_opt_revision_working;
-      SVN_ERR(svn_client__derive_location(&url, &target_rev, target_abspath,
-                                          &peg_revision, ra_session,
-                                          ctx, result_pool, scratch_pool));
+      err = svn_client__derive_location(&url, &target_rev, target_abspath,
+                                        &peg_revision, ra_session,
+                                        ctx, result_pool, scratch_pool);
+
+      if (err)
+        {
+          if (err->apr_err == SVN_ERR_CLIENT_VERSIONED_PATH_REQUIRED)
+            {
+              /* We've been asked to operate on a target which has no location
+               * in the repository. Either it's unversioned (but attempts to
+               * merge into unversioned targets should not get as far as here),
+               * or it is locally added, in which case the target's implicit
+               * mergeinfo is empty. */
+              svn_error_clear(err);
+              *implicit_mergeinfo = apr_hash_make(result_pool);
+              return SVN_NO_ERROR;
+            }
+          else
+            return svn_error_return(err);
+        }
 
       if (target_rev <= end)
         {
@@ -3369,7 +3415,7 @@ get_full_mergeinfo(svn_mergeinfo_t *reco
         }
       else
         {
-          SVN_ERR(svn_client__open_ra_session_internal(&ra_session, url,
+          SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, url,
                                                        NULL, NULL,
                                                        FALSE, TRUE,
                                                        ctx, scratch_pool));
@@ -5258,6 +5304,17 @@ struct get_mergeinfo_walk_baton
 {
   /* Array of paths that have explicit mergeinfo and/or are switched. */
   apr_array_header_t *children_with_mergeinfo;
+
+  /* A hash of MERGE_TARGET_ABSPATH's subdirectories' dirents.  Maps
+     const char * absolute working copy paths to dirent hashes as obtained
+     by svn_io_get_dirents3().  Contents are allocated in CB_POOL. */
+  apr_hash_t *subtree_dirents;
+
+  /* A hash to keep track of any subtrees in the merge target which are
+     unexpectedly missing from disk.  Maps const char * absolute working
+     copy paths to the same.  Contents are allocated in CB_POOL. */
+  apr_hash_t *missing_subtrees;
+
   /* Merge source canonical path. */
   const char* merge_src_canon_path;
 
@@ -5278,8 +5335,111 @@ struct get_mergeinfo_walk_baton
 
   /* Pool from which to allocate new elements of CHILDREN_WITH_MERGEINFO. */
   apr_pool_t *pool;
+
+  /* Pool with a lifetime guaranteed over all the get_mergeinfo_walk_cb
+     callbacks. */
+  apr_pool_t *cb_pool;
 };
 
+/* Helper for the svn_wc__node_found_func_t callback get_mergeinfo_walk_cb().
+
+   Checks for issue #2915 subtrees, i.e. those that the WC thinks are on disk
+   but have been removed due to an OS-level deletion.
+
+   If the supposed working path LOCAL_ABSPATH, of kind KIND, is the root
+   of a missing subtree, then add a (const char *) WC absolute path to
+   (const char *) WC absolute path mapping to MISSING_SUBTREES, where the
+   paths are both a copy of LOCAL_ABSPATH, allocated in RESULT_POOL.
+
+   If LOCAL_ABSPATH is a directory and is not missing from disk, then add
+   a (const char *) WC absolute path to (svn_io_dirent2_t *) dirent mapping
+   to SUBTREE_DIRENTS, again allocated in RESULT_POOL (see
+   svn_io_get_dirents3).
+
+   SCRATCH_POOL is used for temporary allocations.
+
+   Note: Since this is effetively a svn_wc__node_found_func_t callback, it
+   must be called in depth-first order. */
+static svn_error_t *
+record_missing_subtree_roots(const char *local_abspath,
+                             svn_node_kind_t kind,
+                             apr_hash_t *subtree_dirents,
+                             apr_hash_t *missing_subtrees,
+                             apr_pool_t *result_pool,
+                             apr_pool_t *scratch_pool)
+{
+  svn_boolean_t missing_subtree_root = FALSE;
+
+  /* Store the dirents for each directory in SUBTREE_DIRENTS. */
+  if (kind == svn_node_dir)
+    {
+      /* If SUBTREE_DIRENTS is empty LOCAL_ABSPATH is merge target. */
+      if (apr_hash_count(subtree_dirents) == 0 
+          || apr_hash_get(subtree_dirents,
+                          svn_dirent_dirname(local_abspath,
+                                             scratch_pool),
+                                             APR_HASH_KEY_STRING))
+        {
+          apr_hash_t *dirents;
+          svn_error_t *err = svn_io_get_dirents3(&dirents, local_abspath,
+                                                 TRUE, result_pool,
+                                                 scratch_pool);
+          if (err)
+            {
+              if (APR_STATUS_IS_ENOENT(err->apr_err)
+                  || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
+                {
+                  /* We can't get this directory's dirents, it's missing
+                     from disk. */
+                  svn_error_clear(err);
+                  missing_subtree_root = TRUE;
+                }
+              else
+                {
+                  return err;
+                }
+            }
+          else
+            {
+              apr_hash_set(subtree_dirents,
+                           apr_pstrdup(result_pool, local_abspath),
+                           APR_HASH_KEY_STRING, dirents);
+            }      
+        }
+    }
+  else /* kind != svn_node_dir */
+    {
+      /* Is this non-directory missing from disk?  Check LOCAL_ABSPATH's
+         parent's dirents. */
+      apr_hash_t *parent_dirents = apr_hash_get(subtree_dirents,
+                                                svn_dirent_dirname(local_abspath,
+                                                                   scratch_pool),
+                                                APR_HASH_KEY_STRING);
+
+      /* If the parent_dirents is NULL, then LOCAL_ABSPATH is the
+         subtree of a missing subtree.  Since we only report the roots
+         of missing subtrees there is nothing more to do in that case. */
+      if (parent_dirents)
+        {
+          svn_io_dirent2_t *dirent =
+            apr_hash_get(parent_dirents,
+                         svn_dirent_basename(local_abspath, scratch_pool),
+                         APR_HASH_KEY_STRING);
+          if (!dirent)
+            missing_subtree_root = TRUE;
+        }
+    }
+
+  if (missing_subtree_root)
+    {
+      const char *path = apr_pstrdup(result_pool, local_abspath);
+
+      apr_hash_set(missing_subtrees, path,
+                   APR_HASH_KEY_STRING, path);
+    }
+
+  return SVN_NO_ERROR;
+}
 
 /* svn_wc__node_found_func_t callback for get_mergeinfo_paths().
 
@@ -5307,7 +5467,9 @@ get_mergeinfo_walk_cb(const char *local_
   svn_boolean_t is_present;
   svn_boolean_t deleted;
   svn_boolean_t absent;
+#ifndef SVN_WC__SINGLE_DB
   svn_boolean_t obstructed;
+#endif
   svn_boolean_t immediate_child_dir;
 
   /* TODO(#2843) How to deal with a excluded item on merge? */
@@ -5319,14 +5481,20 @@ get_mergeinfo_walk_cb(const char *local_
   if (!is_present)
     return SVN_NO_ERROR;
 
+#ifndef SVN_WC__SINGLE_DB
   SVN_ERR(svn_wc__node_is_status_obstructed(&obstructed, wb->ctx->wc_ctx,
                                             local_abspath, scratch_pool));
+#endif
   SVN_ERR(svn_wc__node_is_status_deleted(&deleted, wb->ctx->wc_ctx,
                                          local_abspath, scratch_pool));
   SVN_ERR(svn_wc__node_is_status_absent(&absent, wb->ctx->wc_ctx,
                                         local_abspath, scratch_pool));
 
+#ifndef SVN_WC__SINGLE_DB
    if (obstructed || deleted || absent)
+#else
+   if (deleted || absent)
+#endif
     {
       propval = NULL;
       switched = FALSE;
@@ -5356,6 +5524,17 @@ get_mergeinfo_walk_cb(const char *local_
                          &&(kind == svn_node_dir)
                          && (strcmp(abs_parent_path,
                                     wb->merge_target_abspath) == 0));
+  /* Make sure what the WC thinks is present on disk really is. */
+#ifndef SVN_WC__SINGLE_DB
+   if (!absent && !deleted && !obstructed)
+#else
+   if (!absent && !deleted)
+#endif
+    SVN_ERR(record_missing_subtree_roots(local_abspath, kind,
+                                         wb->subtree_dirents,
+                                         wb->missing_subtrees,
+                                         wb->cb_pool,
+                                         scratch_pool));
 
   /* Store PATHs with explict mergeinfo, which are switched, are missing
      children due to a sparse checkout, are scheduled for deletion are absent
@@ -5574,7 +5753,7 @@ insert_parent_and_sibs_of_sw_absent_del_
 /* Helper for do_directory_merge()
 
    If HONOR_MERGEINFO is TRUE, then perform a depth first walk of the working
-   copy tree rooted at MERGE_CMD_BATON->TARGET_ABSPATH.
+   copy tree rooted at MERGE_CMD_BATON->TARGET_ABSPATH to depth DEPTH.
    Create an svn_client__merge_path_t * for any path which meets one or more
    of the following criteria:
 
@@ -5602,6 +5781,9 @@ insert_parent_and_sibs_of_sw_absent_del_
    If HONOR_MERGEINFO is FALSE, then create an svn_client__merge_path_t * only
    for MERGE_CMD_BATON->TARGET_ABSPATH (i.e. only criteria 7 is applied).
 
+   If subtrees within the requested DEPTH are unexpectedly missing disk,
+   then raise SVN_ERR_CLIENT_NOT_READY_TO_MERGE.
+
    Store the svn_client__merge_path_t *'s in *CHILDREN_WITH_MERGEINFO in
    depth-first order based on the svn_client__merge_path_t *s path member as
    sorted by svn_path_compare_paths().  Set the remaining_ranges field of each
@@ -5641,6 +5823,9 @@ get_mergeinfo_paths(apr_array_header_t *
   struct get_mergeinfo_walk_baton wb = { 0 };
 
   wb.children_with_mergeinfo = children_with_mergeinfo;
+  wb.cb_pool = svn_pool_create(scratch_pool);
+  wb.subtree_dirents = apr_hash_make(wb.cb_pool);
+  wb.missing_subtrees = apr_hash_make(wb.cb_pool);
   wb.merge_src_canon_path = merge_src_canon_path;
   wb.merge_target_abspath = merge_cmd_baton->target_abspath;
   wb.source_root_url = source_root_url;
@@ -5664,6 +5849,35 @@ get_mergeinfo_paths(apr_array_header_t *
                                      merge_cmd_baton->ctx->cancel_baton,
                                      scratch_pool));
 
+  if (apr_hash_count(wb.missing_subtrees))
+    {
+      apr_hash_index_t *hi;
+      svn_stringbuf_t *missing_subtree_err_buf =
+        svn_stringbuf_create(_("Merge tracking not allowed with missing "
+                               "subtrees; try restoring these items "
+                               "first:\n"), scratch_pool);
+
+      iterpool = svn_pool_create(scratch_pool);
+
+      for (hi = apr_hash_first(scratch_pool, wb.missing_subtrees);
+           hi;
+           hi = apr_hash_next(hi))
+        {
+          svn_pool_clear(iterpool);
+          svn_stringbuf_appendcstr(missing_subtree_err_buf,
+                                   svn_dirent_local_style(
+                                     svn__apr_hash_index_key(hi), iterpool));
+          svn_stringbuf_appendcstr(missing_subtree_err_buf, "\n");
+        }
+
+    return svn_error_create(SVN_ERR_CLIENT_NOT_READY_TO_MERGE,
+                            NULL, missing_subtree_err_buf->data);
+  }
+
+  /* This pool is only needed across all the callbacks to detect
+     missing subtrees. */
+  svn_pool_destroy(wb.cb_pool);
+
   /* CHILDREN_WITH_MERGEINFO must be in depth first order, but the node
      walk code returns nodes in a non particular order.  Also, we may need
      to add elements to the array to cover case 3) through 5) from the
@@ -7166,7 +7380,8 @@ record_mergeinfo_for_dir_merge(svn_merge
      merge target, with no pre-existing explicit mergeinfo, during a --depth
      immediates merge).  Stash those that are inoperative at any depth in
      INOPERATIVE_IMMEDIATE_CHILDREN. */
-  if (!merge_b->record_only && range.start <= range.end)
+  if (!merge_b->record_only && range.start <= range.end
+      && depth == svn_depth_immediates)
     SVN_ERR(get_inoperative_immediate_children(
       &inoperative_immediate_children,
       notify_b->children_with_mergeinfo,
@@ -8173,9 +8388,8 @@ ensure_ra_session_url(svn_ra_session_t *
   if (! *ra_session || (err && err->apr_err == SVN_ERR_RA_ILLEGAL_URL))
     {
       svn_error_clear(err);
-      err = svn_client__open_ra_session_internal(ra_session, url,
-                                                 NULL, NULL,
-                                                 FALSE, TRUE, ctx, pool);
+      err = svn_client__open_ra_session_internal(ra_session, NULL, url, NULL,
+                                                 NULL, FALSE, TRUE, ctx, pool);
     }
   SVN_ERR(err);
 
@@ -8291,6 +8505,11 @@ do_merge(apr_hash_t **modified_subtrees,
 
   SVN_ERR(svn_wc_read_kind(&target_kind, ctx->wc_ctx, target_abspath, FALSE,
                            pool));
+  if (target_kind != svn_node_dir && target_kind != svn_node_file)
+    return svn_error_return(svn_error_createf(
+                              SVN_ERR_ILLEGAL_TARGET, NULL,
+                              _("Merge target '%s' does not exist in the "
+                                "working copy"), target_abspath));
 
   /* Ensure a known depth. */
   if (depth == svn_depth_unknown)
@@ -8668,12 +8887,26 @@ merge_locked(const char *source1,
   apr_pool_t *sesspool;
   svn_boolean_t same_repos;
   const char *source_repos_uuid1, *source_repos_uuid2;
+  svn_node_kind_t target_kind;
+
+  /* Make sure the target is really there. */
+  SVN_ERR(svn_io_check_path(target_abspath, &target_kind, scratch_pool));
+  if (target_kind == svn_node_none)
+    return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+                             _("Path '%s' does not exist"),
+                             svn_dirent_local_style(target_abspath,
+                                                    scratch_pool));
 
-  /* Sanity check our input -- we require specified revisions. */
+  /* Sanity check our input -- we require specified revisions,
+   * and either 2 paths or 2 URLs. */
   if ((revision1->kind == svn_opt_revision_unspecified)
       || (revision2->kind == svn_opt_revision_unspecified))
     return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
                             _("Not all required revisions are specified"));
+  if (svn_path_is_url(source1) != svn_path_is_url(source2))
+    return svn_error_return(svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
+                                             _("Merge sources must both be "
+                                               "either paths or URLs")));
 
   /* ### FIXME: This function really ought to do a history check on
      the left and right sides of the merge source, and -- if one is an
@@ -8701,6 +8934,14 @@ merge_locked(const char *source1,
                              _("'%s' has no URL"),
                              svn_dirent_local_style(source2, scratch_pool));
 
+  SVN_ERR(svn_wc_read_kind(&target_kind, ctx->wc_ctx, target_abspath, FALSE,
+                           scratch_pool));
+  if (target_kind != svn_node_dir && target_kind != svn_node_file)
+    return svn_error_return(svn_error_createf(
+                              SVN_ERR_ILLEGAL_TARGET, NULL,
+                              _("Merge target '%s' does not exist in the "
+                                "working copy"), target_abspath));
+
   /* Determine the working copy target's repository root URL. */
   working_rev.kind = svn_opt_revision_working;
   SVN_ERR(svn_client__get_repos_root(&wc_repos_root, target_abspath,
@@ -8709,12 +8950,12 @@ merge_locked(const char *source1,
 
   /* Open some RA sessions to our merge source sides. */
   sesspool = svn_pool_create(scratch_pool);
-  SVN_ERR(svn_client__open_ra_session_internal(&ra_session1,
-                                               URL1, NULL, NULL,
-                                               FALSE, TRUE, ctx, sesspool));
-  SVN_ERR(svn_client__open_ra_session_internal(&ra_session2,
-                                               URL2, NULL, NULL,
-                                               FALSE, TRUE, ctx, sesspool));
+  SVN_ERR(svn_client__open_ra_session_internal(&ra_session1, NULL, URL1,
+                                               NULL, NULL, FALSE, TRUE,
+                                               ctx, sesspool));
+  SVN_ERR(svn_client__open_ra_session_internal(&ra_session2, NULL, URL2,
+                                               NULL, NULL, FALSE, TRUE,
+                                               ctx, sesspool));
 
   /* Resolve revisions to real numbers. */
   SVN_ERR(svn_client__get_revision_number(&rev1, &youngest_rev, ctx->wc_ctx,
@@ -9931,6 +10172,15 @@ merge_reintegrate_locked(const char *sou
   struct get_subtree_mergeinfo_walk_baton wb;
   const char *target_url;
   svn_revnum_t target_base_rev;
+  svn_node_kind_t kind;
+
+  /* Make sure the target is really there. */
+  SVN_ERR(svn_io_check_path(target_abspath, &kind, scratch_pool));
+  if (kind == svn_node_none)
+    return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+                             _("Path '%s' does not exist"),
+                             svn_dirent_local_style(target_abspath,
+                                                    scratch_pool));
 
   /* Make sure we're dealing with a real URL. */
   SVN_ERR(svn_client_url_from_path2(&url2, source, ctx,
@@ -10001,13 +10251,13 @@ merge_reintegrate_locked(const char *sou
   /* Open two RA sessions, one to our source and one to our target. */
   SVN_ERR(svn_wc__node_get_url(&target_url, ctx->wc_ctx, target_abspath,
                                scratch_pool, scratch_pool));
-  SVN_ERR(svn_client__open_ra_session_internal(&target_ra_session, target_url,
-                                               NULL, NULL, FALSE, FALSE, ctx,
-                                               scratch_pool));
-  SVN_ERR(svn_client__open_ra_session_internal(&source_ra_session, url2,
-                                               NULL, NULL,
-                                               FALSE, FALSE, ctx,
-                                               scratch_pool));
+  SVN_ERR(svn_client__open_ra_session_internal(&target_ra_session, NULL,
+                                               target_url,
+                                               NULL, NULL, FALSE, FALSE,
+                                               ctx, scratch_pool));
+  SVN_ERR(svn_client__open_ra_session_internal(&source_ra_session, NULL,
+                                               url2, NULL, NULL, FALSE, FALSE,
+                                               ctx, scratch_pool));
 
   SVN_ERR(svn_client__get_revision_number(&rev2, NULL, ctx->wc_ctx,
                                           "",
@@ -10191,8 +10441,17 @@ merge_peg_locked(const char *source,
   svn_boolean_t use_sleep = FALSE;
   svn_error_t *err;
   svn_boolean_t same_repos;
+  svn_node_kind_t target_kind;
 
   SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
+  
+  /* Make sure the target is really there. */
+  SVN_ERR(svn_io_check_path(target_abspath, &target_kind, scratch_pool));
+  if (target_kind == svn_node_none)
+    return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+                             _("Path '%s' does not exist"),
+                             svn_dirent_local_style(target_abspath,
+                                                    scratch_pool));
 
   /* Make sure we're dealing with a real URL. */
   SVN_ERR(svn_client_url_from_path2(&URL, source, ctx,
@@ -10202,6 +10461,14 @@ merge_peg_locked(const char *source,
                              _("'%s' has no URL"),
                              svn_dirent_local_style(source, scratch_pool));
 
+  SVN_ERR(svn_wc_read_kind(&target_kind, ctx->wc_ctx, target_abspath, FALSE,
+                           scratch_pool));
+  if (target_kind != svn_node_dir && target_kind != svn_node_file)
+    return svn_error_return(svn_error_createf(
+                              SVN_ERR_ILLEGAL_TARGET, NULL,
+                              _("Merge target '%s' does not exist in the "
+                                "working copy"), target_abspath));
+
   /* Determine the working copy target's repository root URL. */
   working_rev.kind = svn_opt_revision_working;
   SVN_ERR(svn_client__get_repos_root(&wc_repos_root, target_abspath,
@@ -10210,8 +10477,9 @@ merge_peg_locked(const char *source,
 
   /* Open an RA session to our source URL, and determine its root URL. */
   sesspool = svn_pool_create(scratch_pool);
-  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, URL, NULL, NULL,
-                                               FALSE, TRUE, ctx, sesspool));
+  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL, NULL,
+                                               NULL, FALSE, TRUE,
+                                               ctx, sesspool));
   SVN_ERR(svn_ra_get_repos_root2(ra_session, &source_repos_root, scratch_pool));
 
   /* Normalize our merge sources. */

Modified: subversion/branches/performance/subversion/libsvn_client/mergeinfo.c
URL: http://svn.apache.org/viewvc/subversion/branches/performance/subversion/libsvn_client/mergeinfo.c?rev=993141&r1=993140&r2=993141&view=diff
==============================================================================
--- subversion/branches/performance/subversion/libsvn_client/mergeinfo.c (original)
+++ subversion/branches/performance/subversion/libsvn_client/mergeinfo.c Mon Sep  6 20:02:15 2010
@@ -616,11 +616,9 @@ svn_client__get_wc_or_repos_mergeinfo_ca
               else
                 {
                   sesspool = svn_pool_create(scratch_pool);
-                  SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
-                                                               url, NULL, NULL,
-                                                               FALSE, TRUE,
-                                                               ctx,
-                                                               sesspool));
+                  SVN_ERR(svn_client__open_ra_session_internal(
+                              &ra_session, NULL, url, NULL, NULL, FALSE,
+                              TRUE, ctx, sesspool));
                 }
 
               SVN_ERR(svn_client__get_repos_mergeinfo_catalog(
@@ -725,9 +723,9 @@ svn_client__get_history_as_mergeinfo(svn
   if (session == NULL)
     {
       sesspool = svn_pool_create(pool);
-      SVN_ERR(svn_client__open_ra_session_internal(&session, url, NULL, NULL,
-                                                   FALSE, TRUE, ctx,
-                                                   sesspool));
+      SVN_ERR(svn_client__open_ra_session_internal(&session, NULL, url, NULL,
+                                                   NULL, FALSE, TRUE,
+                                                   ctx, sesspool));
     }
 
   /* Fetch the location segments for our URL@PEG_REVNUM. */
@@ -1031,9 +1029,10 @@ get_mergeinfo(svn_mergeinfo_catalog_t *m
       svn_mergeinfo_catalog_t tmp_catalog;
 
       SVN_ERR(svn_dirent_get_absolute(&local_abspath, "", scratch_pool));
-      SVN_ERR(svn_client__open_ra_session_internal(&ra_session, path_or_url,
-                                                   NULL, NULL, FALSE,
-                                                   TRUE, ctx, scratch_pool));
+      SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL,
+                                                   path_or_url, NULL, NULL,
+                                                   FALSE, TRUE, ctx,
+                                                   scratch_pool));
       SVN_ERR(svn_client__get_revision_number(&rev, NULL, ctx->wc_ctx,
                                               local_abspath, ra_session,
                                               &peg_rev, scratch_pool));
@@ -1081,9 +1080,9 @@ get_mergeinfo(svn_mergeinfo_catalog_t *m
       svn_boolean_t indirect;
 
       /* Check server Merge Tracking capability. */
-      SVN_ERR(svn_client__open_ra_session_internal(&ra_session, url,
-                                                   NULL, NULL, FALSE,
-                                                   TRUE, ctx, scratch_pool));
+      SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, url,
+                                                   NULL, NULL, FALSE, TRUE,
+                                                   ctx, scratch_pool));
       SVN_ERR(svn_ra__assert_mergeinfo_capable_server(ra_session, path_or_url,
                                                       scratch_pool));
 

Modified: subversion/branches/performance/subversion/libsvn_client/patch.c
URL: http://svn.apache.org/viewvc/subversion/branches/performance/subversion/libsvn_client/patch.c?rev=993141&r1=993140&r2=993141&view=diff
==============================================================================
--- subversion/branches/performance/subversion/libsvn_client/patch.c (original)
+++ subversion/branches/performance/subversion/libsvn_client/patch.c Mon Sep  6 20:02:15 2010
@@ -48,7 +48,7 @@
 
 typedef struct hunk_info_t {
   /* The hunk. */
-  const svn_hunk_t *hunk;
+  const svn_diff_hunk_t *hunk;
 
   /* The line where the hunk matched in the target file. */
   svn_linenum_t matched_line;
@@ -113,18 +113,26 @@ typedef struct target_content_info_t {
 } target_content_info_t;
 
 typedef struct prop_patch_target_t {
+
+  /* The name of the property */
   const char *name;
+
+  /* All the information that is specific to the content of the property. */
   target_content_info_t *content_info;
+
+  /* Path to the temporary file underlying the result stream. */
   const char *patched_path;
 
+  /* Represents the operation performed on the property. It can be added,
+   * deleted or modified. 
+   * ### Should we use flags instead since we're not using all enum values? */
+  svn_diff_operation_kind_t operation;
+
   /* ### 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;
 
 typedef struct patch_target_t {
-  /* The patch being applied. */
-  const svn_patch_t *patch;
-
   /* The target path as it appeared in the patch file,
    * but in canonicalised form. */
   const char *canon_path_from_patchfile;
@@ -137,7 +145,7 @@ typedef struct patch_target_t {
   /* The absolute path of the target on the filesystem.
    * Any symlinks the path from the patch file may contain are resolved.
    * Is not always known, so it may be NULL. */
-  char *local_abspath;
+  const char *local_abspath;
 
   /* The target file, read-only, seekable. This is NULL in case the target
    * file did not exist prior to patch application. */
@@ -168,6 +176,9 @@ typedef struct patch_target_t {
   /* True if at least one hunk was rejected. */
   svn_boolean_t had_rejects;
 
+  /* 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;
@@ -186,7 +197,13 @@ typedef struct patch_target_t {
   /* True if the target has the executable bit set. */
   svn_boolean_t executable;
 
-  /* All the information that is specifict to the content of the target. */
+  /* True if the patch changed the text of the target. */
+  svn_boolean_t has_text_changes;
+
+  /* True if the patch changed any of the properties of the target. */
+  svn_boolean_t has_prop_changes;
+
+  /* All the information that is specific to the content of the target. */
   target_content_info_t *content_info;
 
   /* A hash table of prop_patch_target_t objects keyed by property names. */
@@ -239,7 +256,8 @@ strip_path(const char **result, const ch
   return SVN_NO_ERROR;
 }
 
-/* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH, from WC_CTX.
+/* Obtain KEYWORDS, EOL_STYLE and EOL_STR for LOCAL_ABSPATH.
+ * WC_CTX is a context for the working copy the patch is applied to.
  * Use RESULT_POOL for allocations of fields in TARGET.
  * Use SCRATCH_POOL for all other allocations. */
 static svn_error_t *
@@ -254,7 +272,6 @@ obtain_eol_and_keywords_for_file(apr_has
   apr_hash_t *props;
   svn_string_t *keywords_val, *eol_style_val;
 
-  /* Handle svn:keyword and svn:eol-style properties. */
   SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath,
                             scratch_pool, scratch_pool));
   keywords_val = apr_hash_get(props, SVN_PROP_KEYWORDS,
@@ -305,6 +322,9 @@ 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).
  * Use RESULT_POOL for allocations of fields in TARGET.
  * Use SCRATCH_POOL for all other allocations. */
 static svn_error_t *
@@ -312,17 +332,24 @@ resolve_target_path(patch_target_t *targ
                     const char *path_from_patchfile,
                     const char *local_abspath,
                     int strip_count,
+                    svn_boolean_t prop_changes_only,
                     svn_wc_context_t *wc_ctx,
                     apr_pool_t *result_pool,
                     apr_pool_t *scratch_pool)
 {
   const char *stripped_path;
+  const char *full_path;
   svn_wc_status3_t *status;
   svn_error_t *err;
+  svn_boolean_t under_root;
 
   target->canon_path_from_patchfile = svn_dirent_internal_style(
                                         path_from_patchfile, result_pool);
-  if (target->canon_path_from_patchfile[0] == '\0')
+
+  /* We allow properties to be set on the wc root dir.
+   * ### Do we need to check for empty paths here, shouldn't the parser
+   * ### guarentee that the paths returned are non-empty? */
+  if (! prop_changes_only && target->canon_path_from_patchfile[0] == '\0')
     {
       /* An empty patch target path? What gives? Skip this. */
       target->skipped = TRUE;
@@ -341,6 +368,7 @@ resolve_target_path(patch_target_t *targ
     {
       target->local_relpath = svn_dirent_is_child(local_abspath, stripped_path,
                                                   result_pool);
+
       if (! target->local_relpath)
         {
           /* The target path is either outside of the working copy
@@ -358,8 +386,11 @@ 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. */
-  if (! svn_dirent_is_under_root(&target->local_abspath, local_abspath,
-                                 target->local_relpath, result_pool))
+  SVN_ERR(svn_dirent_is_under_root(&under_root,
+                                   &full_path, local_abspath,
+                                   target->local_relpath, result_pool));
+
+  if (! under_root)
     {
       /* The target path is outside of the working copy. Skip it. */
       target->skipped = TRUE;
@@ -367,6 +398,9 @@ resolve_target_path(patch_target_t *targ
       return SVN_NO_ERROR;
     }
 
+  SVN_ERR(svn_dirent_get_absolute(&target->local_abspath, full_path,
+                                  result_pool));
+
   /* Skip things we should not be messing with. */
   err = svn_wc_status3(&status, wc_ctx, target->local_abspath,
                        result_pool, scratch_pool);
@@ -403,7 +437,11 @@ resolve_target_path(patch_target_t *targ
     }
   SVN_ERR(svn_wc_read_kind(&target->db_kind, wc_ctx, target->local_abspath,
                            FALSE, scratch_pool));
-  if (target->db_kind == svn_node_dir)
+
+  /* If the target is a versioned directory present on disk,
+   * and there are only property changes in the patch, we accept
+   * a directory target. Else, we skip directories. */
+  if (target->db_kind == svn_node_dir && ! prop_changes_only)
     {
       /* ### We cannot yet replace a locally deleted dir with a file,
        * ### but some day we might want to allow it. */
@@ -429,6 +467,7 @@ resolve_target_path(patch_target_t *targ
 static svn_error_t *
 init_prop_target(prop_patch_target_t **prop_target,
                  const char *prop_name,
+                 svn_diff_operation_kind_t operation,
                  svn_stream_t *reject,
                  svn_boolean_t remove_tempfiles,
                  svn_wc_context_t *wc_ctx,
@@ -439,7 +478,7 @@ init_prop_target(prop_patch_target_t **p
   target_content_info_t *content_info; 
   const svn_string_t *value;
   const char *patched_path;
-
+  svn_error_t *err;
 
   content_info = apr_pcalloc(result_pool, sizeof(*content_info));
 
@@ -455,20 +494,27 @@ init_prop_target(prop_patch_target_t **p
 
   new_prop_target = apr_palloc(result_pool, sizeof(*new_prop_target));
   new_prop_target->name = apr_pstrdup(result_pool, prop_name);
+  new_prop_target->operation = operation;
   new_prop_target->content_info = content_info;
 
-  SVN_ERR(svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name, 
-                           result_pool, scratch_pool));
-
-  if (value)
+  err = svn_wc_prop_get2(&value, wc_ctx, local_abspath, prop_name, 
+                           result_pool, scratch_pool);
+  if (err)
     {
-      /* We assume that a property is small enough to be kept in memory during
-       * the patch process. */
-      content_info->stream = svn_stream_from_string(value, result_pool);
+      if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+        {
+          svn_error_clear(err);
+          value = NULL;
+        }
+      else
+        return svn_error_return(err);
     }
 
+  if (value)
+    content_info->stream = svn_stream_from_string(value, result_pool);
+
   /* Create a temporary file to write the patched result to. For properties,
-   * we don't have to worrry about different eol-style. */
+   * we don't have to worry about eol-style. ### Why not? */
   SVN_ERR(svn_stream_open_unique(&content_info->patched,
                                  &patched_path, NULL,
                                  remove_tempfiles ?
@@ -490,7 +536,8 @@ init_prop_target(prop_patch_target_t **p
  * should be skipped, PATCH_TARGET->SKIPPED is set and the target should be
  * treated as not fully initialized, e.g. the caller should not not do any
  * further operations on the target if it is marked to be skipped.
- * REMOVE_TEMPFILES as in svn_client_patch().
+ * If REMOVE_TEMPFILES is TRUE, set up temporary files to be removed as
+ * soon as they are no longer needed.
  * Use SCRATCH_POOL for all other allocations. */
 static svn_error_t *
 init_patch_target(patch_target_t **patch_target,
@@ -502,6 +549,25 @@ init_patch_target(patch_target_t **patch
 {
   patch_target_t *target;
   target_content_info_t *content_info; 
+  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 = svn__apr_hash_index_val(hi); 
+        if (! has_prop_changes)
+          has_prop_changes = prop_patch->hunks->nelts > 0;
+        else
+          break;
+      }
+  }
+
+  prop_changes_only = has_prop_changes && patch->hunks->nelts == 0;
 
   content_info = apr_pcalloc(result_pool, sizeof(*content_info));
 
@@ -517,7 +583,6 @@ init_patch_target(patch_target_t **patch
   target = apr_pcalloc(result_pool, sizeof(*target));
 
   /* All other fields in target are FALSE or NULL due to apr_pcalloc(). */
-  target->patch = patch;
   target->db_kind = svn_node_none;
   target->kind_on_disk = svn_node_none;
   target->content_info = content_info;
@@ -525,8 +590,8 @@ init_patch_target(patch_target_t **patch
   target->pool = result_pool;
 
   SVN_ERR(resolve_target_path(target, patch->new_filename,
-                              base_dir, strip_count, wc_ctx,
-                              result_pool, scratch_pool));
+                              base_dir, strip_count, prop_changes_only,
+                              wc_ctx, result_pool, scratch_pool));
   if (! target->skipped)
     {
       const char *diff_header;
@@ -534,18 +599,17 @@ init_patch_target(patch_target_t **patch
       apr_size_t len;
       svn_stream_t *patched_raw;
 
+      /* Create a temporary file, and associated streams,
+       * to write the patched result to. */
       if (target->kind_on_disk == svn_node_file)
         {
-          /* Open the file. */
           SVN_ERR(svn_io_file_open(&target->file, target->local_abspath,
                                    APR_READ | APR_BINARY | APR_BUFFERED,
                                    APR_OS_DEFAULT, result_pool));
 
-          /* Create a stream to read from the target. */
           content_info->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->local_abspath, FALSE,
                                           scratch_pool));
@@ -562,7 +626,14 @@ init_patch_target(patch_target_t **patch
                                                    scratch_pool));
         }
 
-      /* Create a temporary file to write the patched result to. */
+      /* ### Is it ok to set target->added here? Isn't the target supposed to
+       * ### be marked as added after it's been proven that it can be added?
+       * ### One alternative is to include a git_added flag. Or maybe we
+       * ### should have kept the patch field in patch_target_t? Then we
+       * ### could have checked for target->patch->operation == added */
+      if (patch->operation == svn_diff_op_added)
+        target->added = TRUE;
+
       SVN_ERR(svn_stream_open_unique(&patched_raw,
                                      &target->patched_path, NULL,
                                      remove_tempfiles ?
@@ -570,16 +641,15 @@ init_patch_target(patch_target_t **patch
                                        svn_io_file_del_none,
                                      result_pool, scratch_pool));
 
-      /* Expand keywords in the patched file.
-       * Repair newlines if svn:eol-style dictates a particular style. */
+      /* We always expand keywords in the patched file, but repair newlines
+       * only if svn:eol-style dictates a particular style. */
       repair_eol = (content_info->eol_style == svn_subst_eol_style_fixed 
                     || content_info->eol_style == svn_subst_eol_style_native);
       content_info->patched = svn_subst_stream_translated(
                               patched_raw, content_info->eol_str, repair_eol,
                               content_info->keywords, TRUE, result_pool);
 
-      /* We'll also need a stream to write rejected hunks to.
-       * We don't expand keywords, nor normalise line-endings,
+      /* We don't expand keywords, nor normalise line-endings,
        * in reject files. */
       SVN_ERR(svn_stream_open_unique(&content_info->reject,
                                      &target->reject_path, NULL,
@@ -597,7 +667,7 @@ init_patch_target(patch_target_t **patch
       len = strlen(diff_header);
       SVN_ERR(svn_stream_write(content_info->reject, diff_header, &len));
 
-      /* Time for the properties */
+      /* Handle properties. */
       if (! target->skipped)
         {
           apr_hash_index_t *hi;
@@ -607,17 +677,17 @@ init_patch_target(patch_target_t **patch
                hi = apr_hash_next(hi))
             {
               const char *prop_name = svn__apr_hash_index_key(hi);
+              svn_prop_patch_t *prop_patch = svn__apr_hash_index_val(hi);
               prop_patch_target_t *prop_target;
 
-              /* Obtain info about this property */
               SVN_ERR(init_prop_target(&prop_target,
                                        prop_name,
+                                       prop_patch->operation,
                                        content_info->reject,
                                        remove_tempfiles,
                                        wc_ctx, target->local_abspath,
                                        result_pool, scratch_pool));
 
-              /* Store the info */
               apr_hash_set(target->prop_targets, prop_name,
                            APR_HASH_KEY_STRING, prop_target);
             }
@@ -730,7 +800,7 @@ seek_to_line(target_content_info_t *cont
  * Do temporary allocations in POOL. */
 static svn_error_t *
 match_hunk(svn_boolean_t *matched, target_content_info_t *content_info,
-           const svn_hunk_t *hunk, int fuzz, 
+           const svn_diff_hunk_t *hunk, int fuzz,
            svn_boolean_t ignore_whitespace,
            svn_boolean_t match_modified, apr_pool_t *pool)
 {
@@ -859,7 +929,7 @@ match_hunk(svn_boolean_t *matched, targe
 static svn_error_t *
 scan_for_match(svn_linenum_t *matched_line, 
                target_content_info_t *content_info,
-               const svn_hunk_t *hunk, svn_boolean_t match_first,
+               const svn_diff_hunk_t *hunk, svn_boolean_t match_first,
                svn_linenum_t upper_line, int fuzz, 
                svn_boolean_t ignore_whitespace,
                svn_boolean_t match_modified,
@@ -931,7 +1001,7 @@ scan_for_match(svn_linenum_t *matched_li
 static svn_error_t *
 match_existing_target(svn_boolean_t *match,
                       target_content_info_t *content_info,
-                      const svn_hunk_t *hunk,
+                      const svn_diff_hunk_t *hunk,
                       svn_stream_t *stream,
                       apr_pool_t *scratch_pool)
 {
@@ -987,14 +1057,16 @@ match_existing_target(svn_boolean_t *mat
  * RESULT_POOL. Use fuzz factor FUZZ. Set HI->FUZZ to FUZZ. If no correct
  * line can be determined, set HI->REJECTED to TRUE.
  * IGNORE_WHITESPACE tells whether whitespace should be considered when
- * matching. When this function returns, neither CONTENT_INFO->CURRENT_LINE nor
+ * matching. IS_PROP_HUNK indicates whether the hunk patches file content
+ * or a property.
+ * When this function returns, neither CONTENT_INFO->CURRENT_LINE nor
  * the file offset in the target file will have changed.
  * Call cancel CANCEL_FUNC with baton CANCEL_BATON to trigger cancellation.
  * Do temporary allocations in POOL. */
 static svn_error_t *
 get_hunk_info(hunk_info_t **hi, patch_target_t *target,
               target_content_info_t *content_info,
-              const svn_hunk_t *hunk, int fuzz, 
+              const svn_diff_hunk_t *hunk, int fuzz,
               svn_boolean_t ignore_whitespace,
               svn_boolean_t is_prop_hunk,
               svn_cancel_func_t cancel_func, void *cancel_baton,
@@ -1030,7 +1102,7 @@ get_hunk_info(hunk_info_t **hi, patch_ta
 
               SVN_ERR(match_existing_target(&file_matches, content_info, hunk,
                                             stream, scratch_pool));
-              svn_stream_close(stream);
+              SVN_ERR(svn_stream_close(stream));
 
               if (file_matches)
                 {
@@ -1225,19 +1297,40 @@ copy_lines_to_target(target_content_info
  * Do temporary allocations in POOL. */
 static svn_error_t *
 reject_hunk(patch_target_t *target, target_content_info_t *content_info, 
-            hunk_info_t *hi, svn_boolean_t is_prop_hunk,  apr_pool_t *pool)
+            hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
 {
   const char *hunk_header;
   apr_size_t len;
   svn_boolean_t eof;
   apr_pool_t *iterpool;
 
-  hunk_header = apr_psprintf(pool, "@@ -%lu,%lu +%lu,%lu @@%s",
-                             svn_diff_hunk_get_original_start(hi->hunk),
-                             svn_diff_hunk_get_original_length(hi->hunk),
-                             svn_diff_hunk_get_modified_start(hi->hunk),
-                             svn_diff_hunk_get_modified_length(hi->hunk),
-                             APR_EOL_STR);
+  if (prop_name)
+    {
+      const char *prop_header;
+        
+      /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Propperty'.
+       */
+      prop_header = apr_psprintf(pool, "Property: %s\n", prop_name);
+      len = strlen(prop_header);
+
+      SVN_ERR(svn_stream_write(content_info->reject, prop_header, &len));
+
+      /* ### What about just setting a variable to either "@@" or "##",
+       * ### and merging with the else clause below? */
+      hunk_header = apr_psprintf(pool, "## -%lu,%lu +%lu,%lu ##%s",
+                                 svn_diff_hunk_get_original_start(hi->hunk),
+                                 svn_diff_hunk_get_original_length(hi->hunk),
+                                 svn_diff_hunk_get_modified_start(hi->hunk),
+                                 svn_diff_hunk_get_modified_length(hi->hunk),
+                                 APR_EOL_STR);
+    }
+  else
+    hunk_header = apr_psprintf(pool, "@@ -%lu,%lu +%lu,%lu @@%s",
+                               svn_diff_hunk_get_original_start(hi->hunk),
+                               svn_diff_hunk_get_original_length(hi->hunk),
+                               svn_diff_hunk_get_modified_start(hi->hunk),
+                               svn_diff_hunk_get_modified_length(hi->hunk),
+                               APR_EOL_STR);
   len = strlen(hunk_header);
   SVN_ERR(svn_stream_write(content_info->reject, hunk_header, &len));
 
@@ -1265,19 +1358,22 @@ reject_hunk(patch_target_t *target, targ
   while (! eof);
   svn_pool_destroy(iterpool);
 
-  /* ### had_rejects is used for notification. We haven't yet turned on
-   * ### notification for properties. */
-  if (! is_prop_hunk)
+  if (prop_name)
+    target->had_prop_rejects = TRUE;
+  else
     target->had_rejects = TRUE;
 
   return SVN_NO_ERROR;
 }
 
-/* Write the modified text of hunk described by HI to the patched
- * stream of CONTENT_INFO. Do temporary allocations in POOL. */
+/* Write the modified text of the hunk described by HI to the patched
+ * stream of CONTENT_INFO. TARGET is the patch target.
+ * If PROP_NAME is not NULL, the hunk is assumed to be targeted for
+ * a property with the given name.
+ * Do temporary allocations in POOL. */
 static svn_error_t *
 apply_hunk(patch_target_t *target, target_content_info_t *content_info,  
-           hunk_info_t *hi, svn_boolean_t is_prop_hunk, apr_pool_t *pool)
+           hunk_info_t *hi, const char *prop_name, apr_pool_t *pool)
 {
   svn_linenum_t lines_read;
   svn_boolean_t eof;
@@ -1285,7 +1381,7 @@ apply_hunk(patch_target_t *target, targe
 
   /* ### Is there a cleaner way to describe if we have an existing target?
    */
-  if (target->kind_on_disk == svn_node_file || is_prop_hunk)
+  if (target->kind_on_disk == svn_node_file || prop_name)
     {
       svn_linenum_t line;
 
@@ -1305,7 +1401,7 @@ apply_hunk(patch_target_t *target, targe
         {
           /* Seek failed, reject this hunk. */
           hi->rejected = TRUE;
-          SVN_ERR(reject_hunk(target, content_info, hi, is_prop_hunk, pool));
+          SVN_ERR(reject_hunk(target, content_info, hi, prop_name, pool));
           return SVN_NO_ERROR;
         }
     }
@@ -1350,6 +1446,53 @@ apply_hunk(patch_target_t *target, targe
   while (! eof);
   svn_pool_destroy(iterpool);
 
+  if (prop_name)
+    target->has_prop_changes = TRUE;
+  else
+    target->has_text_changes = TRUE;
+
+  return SVN_NO_ERROR;
+}
+
+/* Use client context CTX to send a suitable notification for hunk HI,
+ * using TARGET to determine the path. If the hunk is a property hunk,
+ * PROP_NAME must be the name of the property, else NULL.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+send_hunk_notification(const hunk_info_t *hi, 
+                       const patch_target_t *target, 
+                       const char *prop_name, 
+                       const svn_client_ctx_t *ctx,
+                       apr_pool_t *pool)
+{
+  svn_wc_notify_t *notify;
+  svn_wc_notify_action_t action;
+
+  if (hi->already_applied)
+    action = svn_wc_notify_patch_hunk_already_applied;
+  else if (hi->rejected)
+    action = svn_wc_notify_patch_rejected_hunk;
+  else
+    action = svn_wc_notify_patch_applied_hunk;
+
+  notify = svn_wc_create_notify(target->local_abspath
+                                    ? target->local_abspath
+                                    : target->local_relpath,
+                                action, pool);
+  notify->hunk_original_start =
+    svn_diff_hunk_get_original_start(hi->hunk);
+  notify->hunk_original_length =
+    svn_diff_hunk_get_original_length(hi->hunk);
+  notify->hunk_modified_start =
+    svn_diff_hunk_get_modified_start(hi->hunk);
+  notify->hunk_modified_length =
+    svn_diff_hunk_get_modified_length(hi->hunk);
+  notify->hunk_matched_line = hi->matched_line;
+  notify->hunk_fuzz = hi->fuzz;
+  notify->prop_name = prop_name;
+
+  (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+
   return SVN_NO_ERROR;
 }
 
@@ -1396,8 +1539,13 @@ send_patch_notification(const patch_targ
         notify->content_state = svn_wc_notify_state_conflicted;
       else if (target->local_mods)
         notify->content_state = svn_wc_notify_state_merged;
-      else
+      else if (target->has_text_changes)
         notify->content_state = svn_wc_notify_state_changed;
+
+      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;
     }
 
   (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
@@ -1406,39 +1554,44 @@ send_patch_notification(const patch_targ
     {
       int i;
       apr_pool_t *iterpool;
+      apr_hash_index_t *hash_index;
 
       iterpool = svn_pool_create(pool);
       for (i = 0; i < target->content_info->hunks->nelts; i++)
         {
-          hunk_info_t *hi;
+          const hunk_info_t *hi;
 
           svn_pool_clear(iterpool);
 
           hi = APR_ARRAY_IDX(target->content_info->hunks, i, hunk_info_t *);
 
-          if (hi->already_applied)
-            action = svn_wc_notify_patch_hunk_already_applied;
-          else if (hi->rejected)
-            action = svn_wc_notify_patch_rejected_hunk;
-          else
-            action = svn_wc_notify_patch_applied_hunk;
+          SVN_ERR(send_hunk_notification(hi, target, NULL /* prop_name */, 
+                                         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_target = svn__apr_hash_index_val(hash_index);
+
+          for (i = 0; i < prop_target->content_info->hunks->nelts; i++)
+            {
+              const hunk_info_t *hi;
+
+              svn_pool_clear(iterpool);
 
-          notify = svn_wc_create_notify(target->local_abspath
-                                            ? target->local_abspath
-                                            : target->local_relpath,
-                                        action, pool);
-          notify->hunk_original_start =
-            svn_diff_hunk_get_original_start(hi->hunk);
-          notify->hunk_original_length =
-            svn_diff_hunk_get_original_length(hi->hunk);
-          notify->hunk_modified_start =
-            svn_diff_hunk_get_modified_start(hi->hunk);
-          notify->hunk_modified_length =
-            svn_diff_hunk_get_modified_length(hi->hunk);
-          notify->hunk_matched_line = hi->matched_line;
-          notify->hunk_fuzz = hi->fuzz;
+              hi = APR_ARRAY_IDX(prop_target->content_info->hunks, i,
+                                 hunk_info_t *);
 
-          (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
+              /* Don't notify on the hunk level for added or deleted props. */
+              if (prop_target->operation != svn_diff_op_added &&
+                  prop_target->operation != svn_diff_op_deleted)
+                SVN_ERR(send_hunk_notification(hi, target, prop_target->name, 
+                                               ctx, iterpool));
+            }
         }
       svn_pool_destroy(iterpool);
     }
@@ -1500,7 +1653,7 @@ apply_one_patch(patch_target_t **patch_t
   /* Match hunks. */
   for (i = 0; i < patch->hunks->nelts; i++)
     {
-      svn_hunk_t *hunk;
+      svn_diff_hunk_t *hunk;
       hunk_info_t *hi;
       int fuzz = 0;
 
@@ -1509,7 +1662,7 @@ apply_one_patch(patch_target_t **patch_t
       if (cancel_func)
         SVN_ERR((cancel_func)(cancel_baton));
 
-      hunk = APR_ARRAY_IDX(patch->hunks, i, svn_hunk_t *);
+      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. */
@@ -1539,11 +1692,11 @@ apply_one_patch(patch_target_t **patch_t
         continue;
       else if (hi->rejected)
         SVN_ERR(reject_hunk(target, target->content_info, hi,
-                            FALSE /* is_prop_hunk */, 
+                            NULL /* prop_name */, 
                             iterpool));
       else
         SVN_ERR(apply_hunk(target, target->content_info, hi,
-                           FALSE /* is_prop_hunk */, iterpool));
+                           NULL /* prop_name */,  iterpool));
     }
 
   if (target->kind_on_disk == svn_node_file)
@@ -1560,7 +1713,7 @@ apply_one_patch(patch_target_t **patch_t
         }
     }
 
-  /* Match property hunks. */
+  /* Match property hunks.   ### Can we use scratch_pool here? */
   for (hash_index = apr_hash_first(result_pool, patch->prop_patches);
        hash_index;
        hash_index = apr_hash_next(hash_index))
@@ -1570,19 +1723,18 @@ apply_one_patch(patch_target_t **patch_t
       prop_patch_target_t *prop_target;
       target_content_info_t *prop_content_info;
         
-      /* Fetching the parsed info for one property */
       prop_name = svn__apr_hash_index_key(hash_index);
       prop_patch = svn__apr_hash_index_val(hash_index);
 
-      /* Fetch the prop_content_info we'll use to store the matched hunks
-       * in. */
+      /* We'll store matched hunks in prop_content_info.
+       * ### Just use prop_target->content_info? */
       prop_target = apr_hash_get(target->prop_targets, prop_name, 
                                  APR_HASH_KEY_STRING);
       prop_content_info = prop_target->content_info;
 
       for (i = 0; i < prop_patch->hunks->nelts; i++)
         {
-          svn_hunk_t *hunk;
+          svn_diff_hunk_t *hunk;
           hunk_info_t *hi;
           int fuzz = 0;
 
@@ -1591,7 +1743,7 @@ apply_one_patch(patch_target_t **patch_t
           if (cancel_func)
             SVN_ERR((cancel_func)(cancel_baton));
 
-          hunk = APR_ARRAY_IDX(prop_patch->hunks, i, svn_hunk_t *);
+          hunk = APR_ARRAY_IDX(prop_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. */
@@ -1620,7 +1772,9 @@ apply_one_patch(patch_target_t **patch_t
       const char *prop_patched_path;
 
       prop_target = svn__apr_hash_index_val(hash_index);
+      /* ### Just use prop_target->content_info? */
       prop_content_info = prop_target->content_info;
+      /* ### Just use prop_target->patched_path? */
       prop_patched_path = prop_target->patched_path;
 
       for (i = 0; i < prop_content_info->hunks->nelts; i++)
@@ -1634,11 +1788,11 @@ apply_one_patch(patch_target_t **patch_t
             continue;
           else if (hi->rejected)
             SVN_ERR(reject_hunk(target, prop_content_info, hi,
-                                TRUE /* is_prop_hunk */, 
+                                prop_target->name,
                                 iterpool));
           else
             SVN_ERR(apply_hunk(target, prop_content_info, hi, 
-                               TRUE /* is_prop_hunk */,
+                               prop_target->name,
                                iterpool));
         }
 
@@ -1656,7 +1810,10 @@ apply_one_patch(patch_target_t **patch_t
                  * ### dannas: Do we really want to skip an entire target
                  * ### if one of the properties does not apply cleanly,
                  * ### e.g. both text changes and all prop changes will not be
-                 * ### installed. */
+                 * ### installed.
+                 * ### stsp: This is a "should never happen" situation.
+                 * ### It means we've run out of disk space or something
+                 * ### like that, so skipping is appropriate. */
                 target->skipped = TRUE;
               }
           }
@@ -1664,6 +1821,8 @@ apply_one_patch(patch_target_t **patch_t
 
   svn_pool_destroy(iterpool);
 
+  /* ### Move this a separate function? apply_one_patch() has gotten quite
+   * ### big. We should consider splitting it up into several pieces. */
     {
       apr_hash_index_t *hi;
       target_content_info_t *prop_content_info;
@@ -1683,9 +1842,9 @@ apply_one_patch(patch_target_t **patch_t
                * ### stream to read from. Find a better way to store info on
                * ### the existence of the target prop. */
               if (prop_content_info->stream)
-                svn_stream_close(prop_content_info->stream);
+                SVN_ERR(svn_stream_close(prop_content_info->stream));
 
-              svn_stream_close(prop_content_info->patched);
+              SVN_ERR(svn_stream_close(prop_content_info->patched));
             }
 
 
@@ -1726,9 +1885,13 @@ apply_one_patch(patch_target_t **patch_t
       else if (patched_file.size == 0 && working_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_on_disk == svn_node_none)
+           * 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)
@@ -1766,14 +1929,15 @@ create_missing_parents(patch_target_t *t
   apr_pool_t *iterpool;
 
   /* Check if we can safely create the target's parent. */
-  local_abspath = apr_pstrdup(scratch_pool, abs_wc_path);
+  local_abspath = abs_wc_path;
   components = svn_path_decompose(target->local_relpath, scratch_pool);
   present_components = 0;
   iterpool = svn_pool_create(scratch_pool);
   for (i = 0; i < components->nelts - 1; i++)
     {
       const char *component;
-      svn_node_kind_t kind;
+      svn_node_kind_t wc_kind, disk_kind;
+      svn_boolean_t is_deleted;
 
       svn_pool_clear(iterpool);
 
@@ -1781,49 +1945,48 @@ create_missing_parents(patch_target_t *t
                                 const char *);
       local_abspath = svn_dirent_join(local_abspath, component, scratch_pool);
 
-      SVN_ERR(svn_wc_read_kind(&kind, ctx->wc_ctx, local_abspath, TRUE,
+      SVN_ERR(svn_wc_read_kind(&wc_kind, ctx->wc_ctx, local_abspath, TRUE,
                                iterpool));
-      if (kind == svn_node_file)
+
+      SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, iterpool));
+
+      if (wc_kind != svn_node_none)
+        SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted,
+                                               ctx->wc_ctx,
+                                               local_abspath,
+                                               iterpool));
+      else
+        is_deleted = FALSE;
+
+      if (disk_kind == svn_node_file
+          || (wc_kind == svn_node_file && !is_deleted))
         {
-          /* Obstructed. */
+          /* on-disk files and missing files are obstructions */
           target->skipped = TRUE;
           break;
         }
-      else if (kind == svn_node_dir)
+      else if (wc_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_read_kind() call above ignore hidden
-           * nodes.*/
-          svn_boolean_t is_deleted;
-
-          SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted,
-                                                 ctx->wc_ctx,
-                                                 local_abspath,
-                                                 iterpool));
           if (is_deleted)
             {
               target->skipped = TRUE;
               break;
             }
 
+          /* continue one level deeper */
           present_components++;
         }
+      else if (disk_kind == svn_node_dir)
+        {
+          /* Obstructed. ### BH: why? We can just add a directory */
+          target->skipped = TRUE;
+          break;
+        }
       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(local_abspath, &disk_kind,
-                                    iterpool));
-          if (disk_kind != svn_node_none)
-            {
-              /* An unversioned item is in the way. */
-              target->skipped = TRUE;
-              break;
-            }
+          /* It's not a file, it's not a dir...
+             Let's add a dir */
+          break;
         }
     }
 
@@ -1839,6 +2002,15 @@ create_missing_parents(patch_target_t *t
                                           component, scratch_pool);
         }
 
+      if (!dry_run && present_components < components->nelts - 1)
+        SVN_ERR(svn_io_make_dir_recursively(
+                        svn_dirent_join(
+                                   abs_wc_path,
+                                   svn_relpath_dirname(target->local_relpath,
+                                                       scratch_pool),
+                                   scratch_pool),
+                        scratch_pool));
+
       for (i = present_components; i < components->nelts - 1; i++)
         {
           const char *component;
@@ -1869,8 +2041,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. */
-              SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT,
-                                      iterpool));
               SVN_ERR(svn_wc_add4(ctx->wc_ctx, local_abspath,
                                   svn_depth_infinity,
                                   NULL, SVN_INVALID_REVNUM,
@@ -1923,7 +2093,25 @@ install_patched_target(patch_target_t *t
                                    svn_dirent_dirname(target->local_abspath,
                                                       pool),
                                    FALSE, pool));
-          if (parent_db_kind != svn_node_dir)
+
+          /* We don't allow targets to be added under dirs scheduled for
+           * deletion. */
+          if (parent_db_kind == svn_node_dir)
+            {
+              const char *parent_abspath;
+              svn_boolean_t is_deleted;
+              
+              parent_abspath = svn_dirent_dirname(target->local_abspath,
+                                                  pool);
+              SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, ctx->wc_ctx,
+                                                     parent_abspath, pool));
+              if (is_deleted)
+                {
+                  target->skipped = TRUE;
+                  return SVN_NO_ERROR;
+                }
+            }
+          else
             SVN_ERR(create_missing_parents(target, abs_wc_path, ctx,
                                            dry_run, pool));
         }
@@ -1953,15 +2141,144 @@ install_patched_target(patch_target_t *t
         }
     }
 
-  /* Write out rejected hunks, if any. */
-  if (! dry_run && ! target->skipped && target->had_rejects)
+  return SVN_NO_ERROR;
+}
+
+/* Write out rejected hunks, if any, to TARGET->REJECT_PATH. If DRY_RUN is
+ * TRUE, don't modify the working copy.  
+ * Do temporary allocations in POOL.
+ */
+static svn_error_t *
+write_out_rejected_hunks(patch_target_t *target,
+                         svn_boolean_t dry_run,
+                         apr_pool_t *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),
+                               target->local_abspath),
                                FALSE, pool));
       /* ### TODO mark file as conflicted. */
     }
+  return SVN_NO_ERROR;
+}
+
+/* Install the patched properties for TARGET. Use client context CTX to
+ * retrieve WC_CTX. If DRY_RUN is TRUE, don't modify the working copy.
+ * Do temporary allocations in SCRATCH_POOL. */
+static svn_error_t *
+install_patched_prop_targets(patch_target_t *target,
+                             svn_client_ctx_t *ctx, svn_boolean_t dry_run,
+                             apr_pool_t *scratch_pool)
+{
+  apr_hash_index_t *hi;
+  apr_pool_t *iterpool;
+
+  if (dry_run)
+    {
+      if (! target->has_text_changes && target->kind_on_disk == svn_node_none)
+        target->added = TRUE;
+
+      return SVN_NO_ERROR;
+    }
+
+  iterpool = svn_pool_create(scratch_pool);
+
+  for (hi = apr_hash_first(scratch_pool, target->prop_targets);
+       hi;
+       hi = apr_hash_next(hi))
+    {
+      prop_patch_target_t *prop_target = svn__apr_hash_index_val(hi); 
+      apr_file_t *file;
+      svn_stream_t *patched_stream;
+      svn_stringbuf_t *line;
+      svn_stringbuf_t *prop_content;
+      const char *eol_str;
+      svn_boolean_t eof;
+
+      svn_pool_clear(iterpool);
+
+      /* For a deleted prop we only set the value to NULL. */
+      if (prop_target->operation == svn_diff_op_deleted)
+        {
+          SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
+                                   prop_target->name, NULL, 
+                                   TRUE /* skip_checks */,
+                                   NULL, NULL, /* suppress notification */
+                                   iterpool));
+          continue;
+        }
+
+      /* A property is usually small, at most a couple of bytes.
+       * Start out assuming it won't be larger than a typical line of text. */
+      prop_content = svn_stringbuf_create_ensure(80, scratch_pool);
+
+      /* svn_wc_prop_set4() wants a svn_string_t for input so we need to
+       * open the tmp file for reading again.
+       * ### Just keep it open? */
+      SVN_ERR(svn_io_file_open(&file, prop_target->patched_path,
+                               APR_READ | APR_BINARY, APR_OS_DEFAULT,
+                               scratch_pool));
+
+      patched_stream = svn_stream_from_aprfile2(file, FALSE /* disown */,
+                                                iterpool);
+      do
+        {
+          SVN_ERR(svn_stream_readline_detect_eol(patched_stream,
+                                                 &line, &eol_str,
+                                                 &eof,
+                                                 iterpool));
+
+          svn_stringbuf_appendstr(prop_content, line);
+
+          if (eol_str)
+            svn_stringbuf_appendcstr(prop_content, eol_str);
+        }
+      while (! eof);
+
+      SVN_ERR(svn_stream_close(patched_stream));
+
+      /* 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)
+        {
+          SVN_ERR(svn_io_file_create(target->local_abspath, "", scratch_pool));
+          SVN_ERR(svn_wc_add4(ctx->wc_ctx, target->local_abspath,
+                              svn_depth_infinity,
+                              NULL, SVN_INVALID_REVNUM,
+                              ctx->cancel_func,
+                              ctx->cancel_baton,
+                              NULL, NULL, /* suppress notification */
+                              iterpool));
+          target->added = TRUE;
+        }
+
+      /* ### How should we handle SVN_ERR_ILLEGAL_TARGET and
+       * ### SVN_ERR_BAD_MIME_TYPE?
+       *
+       * ### stsp: I'd say reject the property hunk.
+       * ###       We should verify all modified prop hunk texts using
+       * ###       svn_wc_canonicalize_svn_prop() before starting the
+       * ###       patching process, to reject them as early as possible. */
+      SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, target->local_abspath,
+                               prop_target->name,
+                               svn_string_create_from_buf(prop_content, 
+                                                          iterpool),
+                               TRUE /* skip_checks */,
+                               NULL, NULL,
+                               iterpool));
+    }
+
+  svn_pool_destroy(iterpool);
 
   return SVN_NO_ERROR;
 }
@@ -2328,13 +2645,24 @@ apply_patches(void *baton,
                              patch_target_info_t *) = target_info;
 
               if (! target->skipped)
-                SVN_ERR(install_patched_target(target, btn->abs_wc_path,
-                                               btn->ctx, btn->dry_run,
-                                               iterpool));
+                {
+                  if (target->has_text_changes || target->added)
+                    SVN_ERR(install_patched_target(target, btn->abs_wc_path,
+                                                   btn->ctx, btn->dry_run,
+                                                   iterpool));
+
+                  if (target->has_prop_changes)
+                    SVN_ERR(install_patched_prop_targets(target, btn->ctx, 
+                                                         btn->dry_run,
+                                                         iterpool));
+
+                  SVN_ERR(write_out_rejected_hunks(target, btn->dry_run,
+                                                   iterpool));
+                }
               SVN_ERR(send_patch_notification(target, btn->ctx, iterpool));
             }
 
-          SVN_ERR(svn_diff_close_patch(patch));
+          SVN_ERR(svn_diff_close_patch(patch, iterpool));
         }
     }
   while (patch);

Modified: subversion/branches/performance/subversion/libsvn_client/prop_commands.c
URL: http://svn.apache.org/viewvc/subversion/branches/performance/subversion/libsvn_client/prop_commands.c?rev=993141&r1=993140&r2=993141&view=diff
==============================================================================
--- subversion/branches/performance/subversion/libsvn_client/prop_commands.c (original)
+++ subversion/branches/performance/subversion/libsvn_client/prop_commands.c Mon Sep  6 20:02:15 2010
@@ -118,7 +118,8 @@ propset_walk_cb(const char *local_abspat
 
   err = svn_wc_prop_set4(wb->wc_ctx, local_abspath, wb->propname, wb->propval,
                          wb->force, wb->notify_func, wb->notify_baton, pool);
-  if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
+  if (err && (err->apr_err == SVN_ERR_ILLEGAL_TARGET
+              || err->apr_err == SVN_ERR_WC_INVALID_SCHEDULE))
     {
       svn_error_clear(err);
       err = SVN_NO_ERROR;
@@ -195,6 +196,8 @@ propset_on_url(const char *propname,
                svn_boolean_t skip_checks,
                svn_revnum_t base_revision_for_url,
                const apr_hash_t *revprop_table,
+               svn_commit_callback2_t commit_callback,
+               void *commit_baton,
                svn_client_ctx_t *ctx,
                apr_pool_t *pool)
 {
@@ -214,7 +217,7 @@ propset_on_url(const char *propname,
 
   /* Open an RA session for the URL. Note that we don't have a local
      directory, nor a place to put temp files. */
-  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, target,
+  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, target,
                                                NULL, NULL, FALSE, TRUE,
                                                ctx, pool));
 
@@ -269,8 +272,8 @@ propset_on_url(const char *propname,
   /* Fetch RA commit editor. */
   SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
                                     commit_revprops,
-                                    ctx->commit_callback2,
-                                    ctx->commit_baton,
+                                    commit_callback,
+                                    commit_baton,
                                     NULL, TRUE, /* No lock tokens */
                                     pool));
 
@@ -345,6 +348,8 @@ svn_client_propset4(const char *propname
                     svn_revnum_t base_revision_for_url,
                     const apr_array_header_t *changelists,
                     const apr_hash_t *revprop_table,
+                    svn_commit_callback2_t commit_callback,
+                    void *commit_baton,
                     svn_client_ctx_t *ctx,
                     apr_pool_t *pool)
 {
@@ -396,7 +401,8 @@ svn_client_propset4(const char *propname
                                    "'%s' is not supported"), propname, target);
 
       return propset_on_url(propname, propval, target, skip_checks,
-                            base_revision_for_url, revprop_table, ctx, pool);
+                            base_revision_for_url, revprop_table,
+                            commit_callback, commit_baton, ctx, pool);
     }
   else
     {
@@ -471,7 +477,7 @@ svn_client_revprop_set2(const char *prop
 
   /* Open an RA session for the URL. Note that we don't have a local
      directory, nor a place to put temp files. */
-  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, URL, NULL,
+  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL, NULL,
                                                NULL, FALSE, TRUE, ctx, pool));
 
   /* Resolve the revision into something real, and return that to the
@@ -963,7 +969,7 @@ svn_client_revprop_get(const char *propn
 
   /* Open an RA session for the URL. Note that we don't have a local
      directory, nor a place to put temp files. */
-  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, URL, NULL,
+  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL, NULL,
                                                NULL, FALSE, TRUE, ctx, pool));
 
   /* Resolve the revision into something real, and return that to the
@@ -1307,7 +1313,7 @@ svn_client_revprop_list(apr_hash_t **pro
 
   /* Open an RA session for the URL. Note that we don't have a local
      directory, nor a place to put temp files. */
-  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, URL, NULL,
+  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL, NULL,
                                                NULL, FALSE, TRUE, ctx, pool));
 
   /* Resolve the revision into something real, and return that to the