On the 1.7.x-commit-performance branch: sync with 1.7.x@1370416.
git-svn-id: https://svn.apache.org/repos/asf/subversion/branches/1.7.x-commit-performance@1370418 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/subversion/libsvn_client/commit_util.c b/subversion/libsvn_client/commit_util.c
index 2661e39..fdfae49 100644
--- a/subversion/libsvn_client/commit_util.c
+++ b/subversion/libsvn_client/commit_util.c
@@ -261,80 +261,6 @@
       apr_hash_get(committables->by_path, path, APR_HASH_KEY_STRING);
 }
 
-/* Helper for harvest_committables().
- * If ENTRY is a dir, return an SVN_ERR_WC_FOUND_CONFLICT error when
- * encountering a tree-conflicted immediate child node. However, do
- * not consider immediate children that are outside the bounds of DEPTH.
- *
- * TODO ### WC_CTX and LOCAL_ABSPATH ...
- * ENTRY, DEPTH, CHANGELISTS and POOL are the same ones
- * originally received by harvest_committables().
- *
- * Tree-conflicts information is stored in the victim's immediate parent.
- * In some cases of an absent tree-conflicted victim, the tree-conflict
- * information in its parent dir is the only indication that the node
- * is under version control. This function is necessary for this
- * particular case. In all other cases, this simply bails out a little
- * bit earlier. */
-static svn_error_t *
-bail_on_tree_conflicted_children(svn_wc_context_t *wc_ctx,
-                                 const char *local_abspath,
-                                 svn_node_kind_t kind,
-                                 svn_depth_t depth,
-                                 apr_hash_t *changelists,
-                                 svn_wc_notify_func2_t notify_func,
-                                 void *notify_baton,
-                                 apr_pool_t *pool)
-{
-  apr_hash_t *conflicts;
-  apr_hash_index_t *hi;
-
-  if ((depth == svn_depth_empty)
-      || (kind != svn_node_dir))
-    /* There can't possibly be tree-conflicts information here. */
-    return SVN_NO_ERROR;
-
-  SVN_ERR(svn_wc__get_all_tree_conflicts(&conflicts, wc_ctx, local_abspath,
-                                         pool, pool));
-  if (!conflicts)
-    return SVN_NO_ERROR;
-
-  for (hi = apr_hash_first(pool, conflicts); hi; hi = apr_hash_next(hi))
-    {
-      const svn_wc_conflict_description2_t *conflict =
-          svn__apr_hash_index_val(hi);
-
-      if ((conflict->node_kind == svn_node_dir) &&
-          (depth == svn_depth_files))
-        continue;
-
-      /* So we've encountered a conflict that is included in DEPTH.
-         Bail out. But if there are CHANGELISTS, avoid bailing out
-         on an item that doesn't match the CHANGELISTS. */
-      if (!svn_wc__changelist_match(wc_ctx, local_abspath, changelists, pool))
-        continue;
-
-      /* At this point, a conflict was found, and either there were no
-         changelists, or the changelists matched. Bail out already! */
-
-      if (notify_func != NULL)
-        {
-          notify_func(notify_baton,
-                      svn_wc_create_notify(local_abspath,
-                                           svn_wc_notify_failed_conflict,
-                                           pool),
-                      pool);
-        }
-
-      return svn_error_createf(
-               SVN_ERR_WC_FOUND_CONFLICT, NULL,
-               _("Aborting commit: '%s' remains in conflict"),
-               svn_dirent_local_style(conflict->local_abspath, pool));
-    }
-
-  return SVN_NO_ERROR;
-}
-
 /* Helper function for svn_client__harvest_committables().
  * Determine whether we are within a tree-conflicted subtree of the
  * working copy and return an SVN_ERR_WC_FOUND_CONFLICT error if so. */
@@ -419,6 +345,40 @@
 
    Any items added to COMMITTABLES are allocated from the COMITTABLES
    hash pool, not POOL.  SCRATCH_POOL is used for temporary allocations. */
+
+struct harvest_baton
+{
+  /* Static data */
+  svn_client__committables_t *committables;
+  apr_hash_t *lock_tokens;
+  const char *commit_relpath;
+  const char *commit_relpath_abspath; /* Where commit_relpath applies to */
+  const char *copy_mode_root;
+  svn_depth_t depth;
+  svn_boolean_t just_locked;
+  apr_hash_t *changelists;
+  svn_boolean_t skip_files;
+  const char *explicit_target;
+  apr_hash_t *danglers;
+  svn_client__check_url_kind_t check_url_func;
+  void *check_url_baton;
+  svn_cancel_func_t cancel_func;
+  void *cancel_baton;
+  svn_wc_notify_func2_t notify_func;
+  void *notify_baton;
+  svn_wc_context_t *wc_ctx;
+  apr_pool_t *result_pool;
+
+  /* Harvester state */
+  const char *skip_below_abspath; /* If non-NULL, skip everything below */
+};
+
+static svn_error_t *
+harvest_status_callback(void *status_baton,
+                        const char *local_abspath,
+                        const svn_wc_status3_t *status,
+                        apr_pool_t *scratch_pool);
+
 static svn_error_t *
 harvest_committables(svn_wc_context_t *wc_ctx,
                      const char *local_abspath,
@@ -442,6 +402,139 @@
                      apr_pool_t *result_pool,
                      apr_pool_t *scratch_pool)
 {
+  struct harvest_baton baton;
+
+  baton.committables = committables;
+  baton.lock_tokens = lock_tokens;
+  baton.commit_relpath = commit_relpath;
+  baton.commit_relpath_abspath = local_abspath;
+  baton.copy_mode_root = copy_mode_root ? local_abspath : NULL;
+  baton.depth = depth;
+  baton.just_locked = just_locked;
+  baton.changelists = changelists;
+  baton.skip_files = skip_files;
+  baton.explicit_target = local_abspath;
+  baton.danglers = danglers;
+  baton.check_url_func = check_url_func;
+  baton.check_url_baton = check_url_baton;
+  baton.cancel_func = cancel_func;
+  baton.cancel_baton = cancel_baton;
+  baton.notify_func = notify_func;
+  baton.notify_baton = notify_baton;
+  baton.wc_ctx = wc_ctx;
+  baton.result_pool = result_pool;
+
+  baton.skip_below_abspath = NULL;
+
+  SVN_ERR(svn_wc_walk_status(wc_ctx,
+                             local_abspath,
+                             depth,
+                             (commit_relpath != NULL) /* get_all */,
+                             TRUE /* no_ignore */,
+                             FALSE /* ignore_text_mods */,
+                             NULL /* ignore_patterns */,
+                             harvest_status_callback,
+                             &baton,
+                             cancel_func, cancel_baton,
+                             scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+harvest_not_present_for_copy(struct harvest_baton *baton,
+                             const char *local_abspath,
+                             const char *repos_root_url,
+                             const char *commit_relpath,
+                             apr_pool_t *scratch_pool)
+{
+  svn_wc_context_t *wc_ctx = baton->wc_ctx;
+  const apr_array_header_t *children;
+  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+  int i;
+
+  SVN_ERR(svn_wc__node_get_children_of_working_node(
+                                    &children, wc_ctx, local_abspath, TRUE,
+                                    scratch_pool, iterpool));
+      
+  for (i = 0; i < children->nelts; i++)
+    {
+      const char *this_abspath = APR_ARRAY_IDX(children, i, const char *);
+      const char *name = svn_dirent_basename(this_abspath, NULL);
+      const char *this_commit_relpath;
+      svn_boolean_t not_present;
+      svn_node_kind_t kind;
+
+      svn_pool_clear(iterpool);
+
+      SVN_ERR(svn_wc__node_is_status_not_present(¬_present, wc_ctx,
+                                                  this_abspath, scratch_pool));
+
+      if (!not_present)
+        continue;
+
+      if (commit_relpath == NULL)
+        this_commit_relpath = NULL;
+      else
+        this_commit_relpath = svn_relpath_join(commit_relpath, name,
+                                              iterpool);
+
+      /* We should check if we should really add a delete operation */
+      if (baton->check_url_func)
+        {
+          svn_revnum_t rev;
+          const char *repos_relpath;
+          const char *repos_root_url;
+          const char *node_url;
+
+          /* Determine from what parent we would be the deleted child */
+          SVN_ERR(svn_wc__node_get_origin(
+                              NULL, &rev, &repos_relpath,
+                              &repos_root_url, NULL, NULL,
+                              wc_ctx,
+                              svn_dirent_dirname(this_abspath,
+                                                  scratch_pool),
+                              FALSE, scratch_pool, scratch_pool));
+
+          node_url = svn_path_url_add_component2(
+                        svn_path_url_add_component2(repos_root_url,
+                                                    repos_relpath,
+                                                    scratch_pool),
+                        svn_dirent_basename(this_abspath, NULL),
+                        iterpool);
+
+          SVN_ERR(baton->check_url_func(baton->check_url_baton, &kind,
+                                        node_url, rev,
+                                        iterpool));
+
+          if (kind == svn_node_none)
+            continue; /* This node can't be deleted */
+        }
+      else
+        SVN_ERR(svn_wc_read_kind(&kind, wc_ctx, this_abspath, TRUE,
+                                 scratch_pool));
+
+      SVN_ERR(add_committable(baton->committables, this_abspath, kind,
+                              repos_root_url,
+                              this_commit_relpath,
+                              SVN_INVALID_REVNUM,
+                              NULL /* copyfrom_relpath */,
+                              SVN_INVALID_REVNUM /* copyfrom_rev */,
+                              SVN_CLIENT_COMMIT_ITEM_DELETE,
+                              baton->result_pool, scratch_pool));
+    }
+
+  svn_pool_destroy(iterpool);
+  return SVN_NO_ERROR;
+}
+
+/* Implements svn_wc_status_func4_t */
+static svn_error_t *
+harvest_status_callback(void *status_baton,
+                        const char *local_abspath,
+                        const svn_wc_status3_t *status,
+                        apr_pool_t *scratch_pool)
+{
   svn_boolean_t text_mod = FALSE;
   svn_boolean_t prop_mod = FALSE;
   apr_byte_t state_flags = 0;
@@ -466,9 +559,66 @@
   svn_boolean_t is_update_root;
   svn_revnum_t original_rev;
   const char *original_relpath;
-  svn_boolean_t copy_mode = (commit_relpath != NULL);
+  svn_boolean_t copy_mode;
 
-  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+  struct harvest_baton *baton = status_baton;
+  svn_client__committables_t *committables = baton->committables;
+  apr_hash_t *lock_tokens = baton->lock_tokens;
+  const char *repos_root_url = status->repos_root_url;
+  const char *commit_relpath = NULL;
+  svn_boolean_t copy_mode_root = 
+                    (baton->copy_mode_root
+                     && strcmp(baton->copy_mode_root, local_abspath) == 0);
+  svn_depth_t depth = baton->depth;
+  svn_boolean_t just_locked = baton->just_locked;
+  apr_hash_t *changelists = baton->changelists;
+  svn_boolean_t skip_files = baton->skip_files;
+  svn_boolean_t is_explicit_target =
+                    (baton->explicit_target
+                     && strcmp(baton->explicit_target, local_abspath) == 0);
+  apr_hash_t *danglers = baton->danglers;
+  svn_client__check_url_kind_t check_url_func = baton->check_url_func;
+  void *check_url_baton = baton->check_url_baton;
+  svn_cancel_func_t cancel_func = baton->cancel_func;
+  void *cancel_baton = baton->cancel_baton;
+  svn_wc_notify_func2_t notify_func = baton->notify_func;
+  void *notify_baton = baton->notify_baton;
+  svn_wc_context_t *wc_ctx = baton->wc_ctx;
+  apr_pool_t *result_pool = baton->result_pool;
+
+  if (baton->commit_relpath)
+    commit_relpath = svn_relpath_join(
+                        baton->commit_relpath,
+                        svn_dirent_skip_ancestor(baton->commit_relpath_abspath,
+                                                 local_abspath),
+                        scratch_pool);
+
+  copy_mode = (commit_relpath != NULL);
+
+  if (baton->skip_below_abspath
+      && svn_dirent_is_ancestor(baton->skip_below_abspath, local_abspath))
+    {
+      return SVN_NO_ERROR;
+    }
+  else
+    baton->skip_below_abspath = NULL; /* We have left the skip tree */
+
+  switch (status->node_status)
+    {
+      case svn_wc_status_unversioned:
+      case svn_wc_status_ignored:
+      case svn_wc_status_external:
+      case svn_wc_status_none:
+        return SVN_NO_ERROR;
+      case svn_wc_status_normal:
+        if (!copy_mode && !status->conflicted
+            && !(just_locked && status->lock))
+          return SVN_NO_ERROR;
+        break;
+      default:
+        /* Fall through */
+        break;
+    }
 
   /* Early out if the item is already marked as committable. */
   if (look_up_committable(committables, local_abspath, scratch_pool))
@@ -557,31 +707,50 @@
     }
 
   /* If NODE is in our changelist, then examine it for conflicts. We
-     need to bail out if any conflicts exist.  */
+     need to bail out if any conflicts exist.
+     The status walker checked for conflict marker removal. */
   if (conflicted && matches_changelists)
     {
-      svn_boolean_t tc, pc, treec;
+      if (notify_func != NULL)
+        {
+          notify_func(notify_baton,
+                      svn_wc_create_notify(local_abspath,
+                                           svn_wc_notify_failed_conflict,
+                                           scratch_pool),
+                      scratch_pool);
+        }
 
-      SVN_ERR(svn_wc_conflicted_p3(&tc, &pc, &treec, wc_ctx,
-                                   local_abspath, scratch_pool));
-      if (tc || pc || treec)
+      return svn_error_createf(
+            SVN_ERR_WC_FOUND_CONFLICT, NULL,
+            _("Aborting commit: '%s' remains in conflict"),
+            svn_dirent_local_style(local_abspath, scratch_pool));
+    }
+
+  if (status->node_status == svn_wc_status_missing && matches_changelists)
+    {
+      /* Added files and directories must exist. See issue #3198. */
+      if (is_added && is_op_root)
         {
           if (notify_func != NULL)
             {
               notify_func(notify_baton,
                           svn_wc_create_notify(local_abspath,
-                                               svn_wc_notify_failed_conflict,
+                                               svn_wc_notify_failed_missing,
                                                scratch_pool),
                           scratch_pool);
             }
-
           return svn_error_createf(
-            SVN_ERR_WC_FOUND_CONFLICT, NULL,
-            _("Aborting commit: '%s' remains in conflict"),
-            svn_dirent_local_style(local_abspath, scratch_pool));
+             SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+             _("'%s' is scheduled for addition, but is missing"),
+             svn_dirent_local_style(local_abspath, scratch_pool));
         }
+
+      return SVN_NO_ERROR;
     }
 
+  if (status->conflicted && status->kind == svn_node_unknown)
+    return SVN_NO_ERROR; /* Ignored delete-delete conflict */
+
   if (is_deleted && !is_op_root /* && !is_added */)
     return SVN_NO_ERROR; /* Not an operational delete and not an add. */
 
@@ -597,42 +766,6 @@
 
   if (is_deleted || is_replaced)
     state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
-  else if (is_not_present)
-    {
-      if (! copy_mode)
-        return SVN_NO_ERROR;
-
-      /* We should check if we should really add a delete operation */
-      if (check_url_func)
-        {
-          svn_revnum_t revision;
-          const char *repos_relpath;
-          svn_node_kind_t kind;
-
-          /* Determine from what parent we would be the deleted child */
-          SVN_ERR(svn_wc__node_get_origin(NULL, &revision, &repos_relpath,
-                                          NULL, NULL, NULL, wc_ctx,
-                                          svn_dirent_dirname(local_abspath,
-                                                             scratch_pool),
-                                          FALSE, scratch_pool, scratch_pool));
-
-          repos_relpath = svn_relpath_join(repos_relpath,
-                                           svn_dirent_basename(local_abspath,
-                                                               NULL),
-                                           scratch_pool);
-
-          SVN_ERR(check_url_func(check_url_baton, &kind,
-                                 svn_path_url_add_component2(repos_root_url,
-                                                             repos_relpath,
-                                                             scratch_pool),
-                                 revision, scratch_pool));
-
-          if (kind == svn_node_none)
-            return SVN_NO_ERROR; /* This node can't be deleted */
-        }
-
-      state_flags |= SVN_CLIENT_COMMIT_ITEM_DELETE;
-    }
 
   /* Check for adds and copies */
   if (is_added && is_op_root)
@@ -681,23 +814,6 @@
      information about it. */
   if (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)
     {
-      /* First of all, the working file or directory must exist.
-         See issue #3198. */
-      if (working_kind == svn_node_none)
-        {
-          if (notify_func != NULL)
-            {
-              notify_func(notify_baton,
-                          svn_wc_create_notify(local_abspath,
-                                               svn_wc_notify_failed_missing,
-                                               scratch_pool),
-                          scratch_pool);
-            }
-          return svn_error_createf(
-             SVN_ERR_WC_PATH_NOT_FOUND, NULL,
-             _("'%s' is scheduled for addition, but is missing"),
-             svn_dirent_local_style(local_abspath, scratch_pool));
-        }
 
       /* Regular adds of files have text mods, but for copies we have
          to test for textual mods.  Directories simply don't have text! */
@@ -840,60 +956,23 @@
   if (db_kind != svn_node_dir || depth <= svn_depth_empty)
     return SVN_NO_ERROR;
 
-  SVN_ERR(bail_on_tree_conflicted_children(wc_ctx, local_abspath,
-                                           db_kind, depth, changelists,
-                                           notify_func, notify_baton,
-                                           scratch_pool));
+  if ((state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
+      && !(state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
+    {
+      /* Skip all descendants */
+      baton->skip_below_abspath = apr_pstrdup(baton->result_pool,
+                                              local_abspath);
+      return SVN_NO_ERROR;
+    }
 
   /* Recursively handle each node according to depth, except when the
-     node is only being deleted. */
-  if ((! (state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE))
-      || (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
+     node is only being deleted, or is in an added tree (as added trees
+     use the normal commit handling). */
+  if (copy_mode && !is_added && !is_deleted)
     {
-      const apr_array_header_t *children;
-      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
-      int i;
-      svn_depth_t depth_below_here = depth;
-
-      if (depth < svn_depth_infinity)
-        depth_below_here = svn_depth_empty; /* Stop recursing */
-
-      SVN_ERR(svn_wc__node_get_children_of_working_node(
-                &children, wc_ctx, local_abspath, copy_mode,
-                scratch_pool, iterpool));
-      for (i = 0; i < children->nelts; i++)
-        {
-          const char *this_abspath = APR_ARRAY_IDX(children, i, const char *);
-          const char *name = svn_dirent_basename(this_abspath, NULL);
-          const char *this_commit_relpath;
-
-          svn_pool_clear(iterpool);
-
-          if (commit_relpath == NULL)
-            this_commit_relpath = NULL;
-          else
-            this_commit_relpath = svn_relpath_join(commit_relpath, name,
-                                                   iterpool);
-
-          SVN_ERR(harvest_committables(wc_ctx, this_abspath,
-                                       committables, lock_tokens,
-                                       repos_root_url,
-                                       this_commit_relpath,
-                                       FALSE, /* COPY_MODE_ROOT */
-                                       depth_below_here,
-                                       just_locked,
-                                       changelists,
-                                       (depth < svn_depth_files),
-                                       (depth < svn_depth_immediates),
-                                       NULL, /* danglers */
-                                       check_url_func, check_url_baton,
-                                       cancel_func, cancel_baton,
-                                       notify_func, notify_baton,
-                                       result_pool,
-                                       iterpool));
-        }
-
-      svn_pool_destroy(iterpool);
+      SVN_ERR(harvest_not_present_for_copy(baton, local_abspath,
+                                           repos_root_url, commit_relpath,
+                                           scratch_pool));
     }
 
   return SVN_NO_ERROR;
diff --git a/subversion/tests/cmdline/lock_tests.py b/subversion/tests/cmdline/lock_tests.py
index 860333c..f384523 100755
--- a/subversion/tests/cmdline/lock_tests.py
+++ b/subversion/tests/cmdline/lock_tests.py
@@ -159,19 +159,21 @@
   wc_dir = sbox.wc_dir
 
   fname = 'A/mu'
-  file_path = os.path.join(sbox.wc_dir, fname)
+  file_path = sbox.ospath(fname)
 
-  # lock fname as wc_author
+  # lock fname and iota as wc_author
   svntest.actions.run_and_verify_svn(None, ".*locked by user", [], 'lock',
-                                     '-m', 'some lock comment', file_path)
+                                     '-m', 'some lock comment',
+                                     sbox.ospath(fname),
+                                     sbox.ospath('iota'))
 
   # make a change and commit it, allowing lock to be released
   svntest.main.file_append(file_path, "Tweak!\n")
-  svntest.main.run_svn(None, 'commit', '-m', '',
-                       file_path)
+  svntest.main.run_svn(None, 'commit', '-m', '', wc_dir)
 
   expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
   expected_status.tweak(fname, wc_rev=2)
+  expected_status.tweak('iota', wc_rev=2)
 
   # Make sure the file is unlocked
   svntest.actions.run_and_verify_status(wc_dir, expected_status)