You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2013/02/13 07:37:56 UTC

svn commit: r1445479 [3/11] - in /subversion/branches/fsfs-format7: ./ build/generator/ build/generator/swig/ build/generator/templates/ notes/api-errata/1.7/ packages/ subversion/bindings/swig/include/ subversion/bindings/swig/perl/libsvn_swig_perl/ s...

Modified: subversion/branches/fsfs-format7/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/libsvn_client/merge.c?rev=1445479&r1=1445478&r2=1445479&view=diff
==============================================================================
--- subversion/branches/fsfs-format7/subversion/libsvn_client/merge.c (original)
+++ subversion/branches/fsfs-format7/subversion/libsvn_client/merge.c Wed Feb 13 06:37:54 2013
@@ -198,9 +198,9 @@
  * merge target and any of its subtrees which have explicit mergeinfo
  * or otherwise need special attention during a merge.
  *
- * During mergeinfo unaware merges, CHILDREN_WITH_MERGEINFO is created by
- * do_mergeinfo_unaware_dir_merge and contains only one element describing
- * a contiguous range to be merged to the WC merge target.
+ * During mergeinfo unaware merges, CHILDREN_WITH_MERGEINFO contains
+ * contains only one element (added by do_mergeinfo_unaware_dir_merge)
+ * describing a contiguous range to be merged to the WC merge target.
  *
  * During mergeinfo aware merges CHILDREN_WITH_MERGEINFO is created
  * by get_mergeinfo_paths() and outside of that function and its helpers
@@ -235,9 +235,6 @@ typedef struct merge_target_t
   /* Absolute path to the WC node */
   const char *abspath;
 
-  /* Node kind of the WC node (at the start of the merge) */
-  svn_node_kind_t kind;
-
   /* The repository location of the base node of the target WC.  If the node
    * is locally added, then URL & REV are NULL & SVN_INVALID_REVNUM.
    * REPOS_ROOT_URL and REPOS_UUID are always valid. */
@@ -266,9 +263,6 @@ typedef struct merge_cmd_baton_t {
                                          merge or not. */
   const merge_target_t *target;       /* Description of merge target node */
 
-  const svn_merge_range_t *current_range; /* For single file merges the current
-                                             revision range */
-
   /* The left and right URLs and revs.  The value of this field changes to
      reflect the merge_source_t *currently* being merged by do_merge(). */
   merge_source_t merge_source;
@@ -348,26 +342,18 @@ typedef struct merge_cmd_baton_t {
      or do_file_merge() in do_merge(). */
   apr_pool_t *pool;
 
-  /* Boolean indicating whether the information header for the current
-     operation has already been notified */
-  svn_boolean_t notified_merge_begin;
-
-  /* Contains any state collected while receiving path notifications. */
-
-  /* Flag indicating whether it is a single file merge or not. */
-  svn_boolean_t is_single_file_merge;
-
-  /* Depth first ordered list of paths that needs special care while merging.
-     ### And ...? This is not just a list of paths. See the global comment
-         'THE CHILDREN_WITH_MERGEINFO ARRAY'.
-     This defaults to NULL. For 'same_url' merge alone we set it to
-     proper array. This is used by notification_receiver to put a
-     merge notification begin lines. */
-  apr_array_header_t *children_with_mergeinfo;
 
-  /* The path in CHILDREN_WITH_MERGEINFO where we found the nearest ancestor
-     for merged path. Default value is null. */
-  const char *cur_ancestor_abspath;
+  /* State for notify_merge_begin() */
+  struct notify_begin_state_t
+  {
+    /* Cache of which abspath was last notified. */
+    const char *last_abspath;
+
+    /* Reference to the one-and-only CHILDREN_WITH_MERGEINFO (see global
+       comment) or a similar list for single-file-merges */
+    const apr_array_header_t *nodes_with_mergeinfo;
+  } notify_begin;
+
 } merge_cmd_baton_t;
 
 
@@ -500,7 +486,7 @@ store_path(apr_hash_t *path_hash, const 
   const char *dup_path = apr_pstrdup(apr_hash_pool_get(path_hash),
                                      local_abspath);
 
-  apr_hash_set(path_hash, dup_path, APR_HASH_KEY_STRING, dup_path);
+  svn_hash_sets(path_hash, dup_path, dup_path);
 }
 
 /* Store LOCAL_ABSPATH in *PATH_HASH_P after duplicating it into the pool
@@ -516,13 +502,6 @@ alloc_and_store_path(apr_hash_t **path_h
   store_path(*path_hash_p, local_abspath);
 }
 
-/* Helper function to easy in checking if a path is in a path hash */
-static APR_INLINE svn_boolean_t
-contains_path(apr_hash_t *path_hash, const char *local_abspath)
-{
-  return apr_hash_get(path_hash, local_abspath, APR_HASH_KEY_STRING) != NULL;
-}
-
 /* Return whether any WC path was put in conflict by the merge
    operation corresponding to MERGE_B. */
 static APR_INLINE svn_boolean_t
@@ -550,10 +529,11 @@ is_path_conflicted_by_merge(merge_cmd_ba
 static svn_error_t *
 perform_obstruction_check(svn_wc_notify_state_t *obstruction_state,
                           svn_boolean_t *deleted,
+                          svn_boolean_t *excluded,
                           svn_node_kind_t *kind,
+                          svn_depth_t *parent_depth,
                           const merge_cmd_baton_t *merge_b,
                           const char *local_abspath,
-                          svn_node_kind_t expected_kind,
                           apr_pool_t *scratch_pool)
 {
   svn_wc_context_t *wc_ctx = merge_b->ctx->wc_ctx;
@@ -577,24 +557,18 @@ perform_obstruction_check(svn_wc_notify_
   SVN_ERR(svn_wc__check_for_obstructions(obstruction_state,
                                          kind,
                                          deleted,
+                                         excluded,
+                                         parent_depth,
                                          wc_ctx, local_abspath,
                                          check_root,
                                          scratch_pool));
-
-  if (*obstruction_state == svn_wc_notify_state_inapplicable
-      && expected_kind != svn_node_unknown
-      && *kind != expected_kind)
-    {
-      *obstruction_state = svn_wc_notify_state_obstructed;
-    }
-
   return SVN_NO_ERROR;
 }
 
 /* Create *LEFT and *RIGHT conflict versions for conflict victim
  * at VICTIM_ABSPATH, with kind NODE_KIND, using information obtained
  * from MERGE_SOURCE and TARGET.
- * Allocate returned conflict versions in POOL. */
+ * Allocate returned conflict versions in RESULT_POOL. */
 static svn_error_t *
 make_conflict_versions(const svn_wc_conflict_version_t **left,
                        const svn_wc_conflict_version_t **right,
@@ -602,7 +576,8 @@ make_conflict_versions(const svn_wc_conf
                        svn_node_kind_t node_kind,
                        const merge_source_t *merge_source,
                        const merge_target_t *target,
-                       apr_pool_t *pool)
+                       apr_pool_t *result_pool,
+                       apr_pool_t *scratch_pool)
 {
   const char *child = svn_dirent_skip_ancestor(target->abspath,
                                                victim_abspath);
@@ -610,186 +585,25 @@ make_conflict_versions(const svn_wc_conf
 
   SVN_ERR_ASSERT(child != NULL);
   left_relpath = svn_client__pathrev_relpath(merge_source->loc1,
-                                             pool);
+                                             scratch_pool);
   right_relpath = svn_client__pathrev_relpath(merge_source->loc2,
-                                              pool);
+                                              scratch_pool);
 
   *left = svn_wc_conflict_version_create2(
             merge_source->loc1->repos_root_url,
             merge_source->loc1->repos_uuid,
-            svn_relpath_join(left_relpath, child, pool),
-            merge_source->loc1->rev, node_kind, pool);
+            svn_relpath_join(left_relpath, child, scratch_pool),
+            merge_source->loc1->rev, node_kind, result_pool);
 
   *right = svn_wc_conflict_version_create2(
              merge_source->loc2->repos_root_url,
              merge_source->loc2->repos_uuid,
-             svn_relpath_join(right_relpath, child, pool),
-             merge_source->loc2->rev, node_kind, pool);
-
-  return SVN_NO_ERROR;
-}
-
-/* Set *CONFLICT to a new tree-conflict description allocated in POOL,
- * populated with information from the other parameters.
- * See tree_conflict() for the other parameters.
- */
-static svn_error_t*
-make_tree_conflict(svn_wc_conflict_description2_t **conflict,
-                   const char *victim_abspath,
-                   svn_node_kind_t node_kind,
-                   svn_wc_conflict_action_t action,
-                   svn_wc_conflict_reason_t reason,
-                   const merge_source_t *merge_source,
-                   const merge_target_t *target,
-                   apr_pool_t *pool)
-{
-  const svn_wc_conflict_version_t *left;
-  const svn_wc_conflict_version_t *right;
-
-  SVN_ERR(make_conflict_versions(&left, &right, victim_abspath, node_kind,
-                                 merge_source, target, pool));
-
-  *conflict = svn_wc_conflict_description_create_tree2(
-                    victim_abspath, node_kind, svn_wc_operation_merge,
-                    left, right, pool);
-
-  (*conflict)->action = action;
-  (*conflict)->reason = reason;
-
-  return SVN_NO_ERROR;
-}
-
-/* Record a tree conflict in the WC, unless this is a dry run or a record-
- * only merge, or if a tree conflict is already flagged for the VICTIM_PATH.
- * (The latter can happen if a merge-tracking-aware merge is doing multiple
- * editor drives because of a gap in the range of eligible revisions.)
- *
- * The tree conflict, with its victim specified by VICTIM_PATH, is
- * assumed to have happened during a merge using merge baton MERGE_B.
- *
- * NODE_KIND must be the node kind of "old" and "theirs" and "mine";
- * this function cannot cope with node kind clashes.
- * ACTION and REASON correspond to the fields
- * of the same names in svn_wc_tree_conflict_description_t.
- */
-static svn_error_t*
-tree_conflict(merge_cmd_baton_t *merge_b,
-              const char *victim_abspath,
-              svn_node_kind_t node_kind,
-              svn_wc_conflict_action_t action,
-              svn_wc_conflict_reason_t reason,
-              const char *moved_away_op_root_abspath)
-{
-  const svn_wc_conflict_description2_t *existing_conflict;
-  svn_error_t *err;
-
-  if (merge_b->record_only || merge_b->dry_run)
-    return SVN_NO_ERROR;
-
-  err = svn_wc__get_tree_conflict(&existing_conflict, merge_b->ctx->wc_ctx,
-                                  victim_abspath, merge_b->pool,
-                                  merge_b->pool);
-
-  if (err)
-    {
-      if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
-        return svn_error_trace(err);
-
-      svn_error_clear(err);
-      existing_conflict = FALSE;
-    }
-
-  if (existing_conflict == NULL)
-    {
-      svn_wc_conflict_description2_t *conflict;
-
-      /* There is no existing tree conflict so it is safe to add one. */
-      SVN_ERR(make_tree_conflict(&conflict, victim_abspath,
-                                 node_kind, action, reason,
-                                 &merge_b->merge_source, merge_b->target,
-                                 merge_b->pool));
-      SVN_ERR(svn_wc__add_tree_conflict(merge_b->ctx->wc_ctx, conflict,
-                                        moved_away_op_root_abspath,
-                                        merge_b->pool));
-
-      alloc_and_store_path(&merge_b->conflicted_paths, victim_abspath,
-                           merge_b->pool);
-    }
+             svn_relpath_join(right_relpath, child, scratch_pool),
+             merge_source->loc2->rev, node_kind, result_pool);
 
   return SVN_NO_ERROR;
 }
 
-/* Similar to tree_conflict(), but if this is an "add" action and there
-   is an existing tree conflict on the victim with a "delete" action, then
-   combine the two conflicts into a single conflict with a "replace" action. */
-static svn_error_t*
-tree_conflict_on_add(merge_cmd_baton_t *merge_b,
-                     const char *victim_abspath,
-                     svn_node_kind_t node_kind,
-                     svn_wc_conflict_action_t action,
-                     svn_wc_conflict_reason_t reason)
-{
-  const svn_wc_conflict_description2_t *existing_conflict;
-  svn_wc_conflict_description2_t *conflict;
-
-  if (merge_b->record_only || merge_b->dry_run)
-    return SVN_NO_ERROR;
-
-  /* Construct the new conflict first  compare the new conflict with
-     a possibly existing one. */
-  SVN_ERR(make_tree_conflict(&conflict, victim_abspath,
-                             node_kind, action, reason,
-                             &merge_b->merge_source, merge_b->target,
-                             merge_b->pool));
-
-  SVN_ERR(svn_wc__get_tree_conflict(&existing_conflict, merge_b->ctx->wc_ctx,
-                                    victim_abspath, merge_b->pool,
-                                    merge_b->pool));
-
-  if (existing_conflict == NULL)
-    {
-      /* There is no existing tree conflict so it is safe to add one. */
-      SVN_ERR(svn_wc__add_tree_conflict(merge_b->ctx->wc_ctx, conflict,
-                                        NULL,
-                                        merge_b->pool));
-
-      alloc_and_store_path(&merge_b->conflicted_paths, victim_abspath,
-                           merge_b->pool);
-    }
-  else if (existing_conflict->action == svn_wc_conflict_action_delete &&
-           conflict->action == svn_wc_conflict_action_add)
-    {
-      /* There is already a tree conflict raised by a previous incoming
-       * change that attempted to delete the item (whether in this same
-       * merge operation or not). Change the existing conflict to note
-       * that the incoming change is replacement. */
-
-      /* Remove the existing tree-conflict so we can add a new one.*/
-      SVN_ERR(svn_wc__del_tree_conflict(merge_b->ctx->wc_ctx,
-                                        victim_abspath, merge_b->pool));
-
-      /* Preserve the reason which caused the first conflict,
-       * re-label the incoming change as 'replacement', and update
-       * version info for the left version of the conflict. */
-      conflict->reason = existing_conflict->reason;
-      conflict->action = svn_wc_conflict_action_replace;
-      conflict->src_left_version = svn_wc_conflict_version_dup(
-                                     existing_conflict->src_left_version,
-                                     merge_b->pool);
-
-      SVN_ERR(svn_wc__add_tree_conflict(merge_b->ctx->wc_ctx, conflict,
-                                        NULL,
-                                        merge_b->pool));
-
-      alloc_and_store_path(&merge_b->conflicted_paths, victim_abspath,
-                           merge_b->pool);
-    }
-
-  /* In any other cases, we don't touch the existing conflict. */
-  return SVN_NO_ERROR;
-}
-
-
 /* Helper for filter_self_referential_mergeinfo()
 
    *MERGEINFO is a non-empty, non-null collection of mergeinfo.
@@ -863,8 +677,8 @@ split_mergeinfo_on_revision(svn_mergeinf
                  ranges from *MERGEINFO */
               if (!(*younger_mergeinfo))
                 *younger_mergeinfo = apr_hash_make(pool);
-              apr_hash_set(*younger_mergeinfo, merge_source_path,
-                           APR_HASH_KEY_STRING, younger_rangelist);
+              svn_hash_sets(*younger_mergeinfo, merge_source_path,
+                            younger_rangelist);
               SVN_ERR(svn_mergeinfo_remove2(mergeinfo, *younger_mergeinfo,
                                             *mergeinfo, TRUE, pool, iterpool));
               break; /* ...out of for (i = 0; i < rangelist->nelts; i++) */
@@ -1134,8 +948,8 @@ filter_self_referential_mergeinfo(apr_ar
                 {
                   if (!filtered_younger_mergeinfo)
                     filtered_younger_mergeinfo = apr_hash_make(iterpool);
-                  apr_hash_set(filtered_younger_mergeinfo, source_path,
-                               APR_HASH_KEY_STRING, adjusted_rangelist);
+                  svn_hash_sets(filtered_younger_mergeinfo, source_path,
+                                adjusted_rangelist);
                 }
 
             } /* Iteration over each merge source in younger_mergeinfo. */
@@ -1266,10 +1080,10 @@ prepare_merge_props_changed(const apr_ar
     }
   *prop_updates = props;
 
-  /* If this is not a dry run then make a record in BATON if we find a
-     PATH where mergeinfo is added where none existed previously or PATH
-     is having its existing mergeinfo deleted. */
-  if (!merge_b->dry_run && props->nelts)
+  /* Make a record in BATON if we find a PATH where mergeinfo is added
+     where none existed previously or PATH is having its existing
+     mergeinfo deleted. */
+  if (props->nelts)
     {
       int i;
 
@@ -1290,8 +1104,7 @@ prepare_merge_props_changed(const apr_ar
                                                 scratch_pool));
 
               if (pristine_props
-                  && apr_hash_get(pristine_props, SVN_PROP_MERGEINFO,
-                                  APR_HASH_KEY_STRING))
+                  && svn_hash_gets(pristine_props, SVN_PROP_MERGEINFO))
                 has_pristine_mergeinfo = TRUE;
 
               if (!has_pristine_mergeinfo && prop->value)
@@ -1311,62 +1124,24 @@ prepare_merge_props_changed(const apr_ar
   return SVN_NO_ERROR;
 }
 
-/* Set *OP_ROOT_ABSPATH if the node at LOCAL_ABSPATH was moved away
- * locally, or set *OP_ROOT_ABSPATH to NULL otherwise. Do not raise an
- * error if the node at LOCAL_ABSPATH does not exist.
- *
- * ### Currently this looks at the highest moved-to but should merge
- * ### be looking at the lowest moved-to?  Or perhaps only the base
- * ### moved-to? */
-static svn_error_t *
-check_moved_away(const char **op_root_abspath,
-                 svn_wc_context_t *wc_ctx,
-                 const char *local_abspath,
-                 apr_pool_t *scratch_pool)
-{
-  const char *moved_to_abspath;
-
-  SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, op_root_abspath,
-                                      wc_ctx, local_abspath,
-                                      scratch_pool, scratch_pool));
-  
-  return SVN_NO_ERROR;
-}
-
-/* Indicate in *MOVED_HERE whether the node at LOCAL_ABSPATH was
- * moved here locally. Do not raise an error if the node at LOCAL_ABSPATH
- * does not exist. */
-static svn_error_t *
-check_moved_here(svn_boolean_t *moved_here,
-                 svn_wc_context_t *wc_ctx,
-                 const char *local_abspath,
-                 apr_pool_t *scratch_pool)
-{
-  const char *moved_from_abspath;
-  svn_error_t *err;
-
-  *moved_here = FALSE;
-
-  err = svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
-                                    wc_ctx, local_abspath,
-                                    scratch_pool, scratch_pool);
-  if (err)
-    {
-      if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
-        svn_error_clear(err);
-      else
-        return svn_error_trace(err);
-    }
-  else if (moved_from_abspath)
-    *moved_here = TRUE;
-
-  return SVN_NO_ERROR;
-}
-
 #define CONFLICT_REASON_NONE       ((svn_wc_conflict_reason_t)-1)
-#define CONFLICT_REASON_EXCLUDED   ((svn_wc_conflict_reason_t)-2)
-#define CONFLICT_REASON_SKIP       ((svn_wc_conflict_reason_t)-3)
-#define CONFLICT_REASON_SKIP_WC    ((svn_wc_conflict_reason_t)-4)
+#define CONFLICT_REASON_SKIP       ((svn_wc_conflict_reason_t)-2)
+#define CONFLICT_REASON_SKIP_WC    ((svn_wc_conflict_reason_t)-3)
+
+/* Baton used for testing trees for being editted while performing tree
+   conflict detection for incoming deletes */
+struct dir_delete_baton_t
+{
+  /* Reference to dir baton of directory that is the root of the deletion */
+  struct merge_dir_baton_t *del_root;
+
+  /* Boolean indicating that some edit is found. Allows avoiding more work */
+  svn_boolean_t found_edit;
+
+  /* A list of paths that are compared. Kept up to date until FOUND_EDIT is
+     set to TRUE */
+  apr_hash_t *compared_abspaths;
+};
 
 /* Baton for the merge_dir_*() functions. Initialized in merge_dir_opened() */
 struct merge_dir_baton_t
@@ -1394,10 +1169,6 @@ struct merge_dir_baton_t
      conflict.
 
      Special values:
-       CONFLICT_REASON_EXCLUDED:
-            The node has been excluded (or depth filtered) and the node will
-            only be reported as skipped.
-
        CONFLICT_REASON_SKIP:
             The node will be skipped with content and property state as stored in
             SKIP_REASON.
@@ -1425,6 +1196,16 @@ struct merge_dir_baton_t
      notification (which may be a replaced notification if the node is not just
      deleted) */
   apr_hash_t *pending_deletes;
+
+  /* NULL, or an hashtable mapping const char * LOCAL_ABSPATHs to
+     a const svn_wc_conflict_description2_t * instance, describing the just
+     installed conflict */
+  apr_hash_t *new_tree_conflicts;
+
+  /* If not NULL, a reference to the information of the delete test that is
+     currently in progress. Allocated in the root-directory baton, referenced
+     from all descendants */
+  struct dir_delete_baton_t *delete_state;
 };
 
 /* Baton for the merge_dir_*() functions. Initialized in merge_file_opened() */
@@ -1500,18 +1281,113 @@ record_skip(merge_cmd_baton_t *merge_b,
   return SVN_NO_ERROR;
 }
 
-/* Record the skip for future processing and (later) produce the
-   tree conflict notification */
+/* Record a tree conflict in the WC, unless this is a dry run or a record-
+ * only merge, or if a tree conflict is already flagged for the VICTIM_PATH.
+ * (The latter can happen if a merge-tracking-aware merge is doing multiple
+ * editor drives because of a gap in the range of eligible revisions.)
+ *
+ * The tree conflict, with its victim specified by VICTIM_PATH, is
+ * assumed to have happened during a merge using merge baton MERGE_B.
+ *
+ * NODE_KIND must be the node kind of "old" and "theirs" and "mine";
+ * this function cannot cope with node kind clashes.
+ * ACTION and REASON correspond to the fields
+ * of the same names in svn_wc_tree_conflict_description_t.
+ */
 static svn_error_t *
 record_tree_conflict(merge_cmd_baton_t *merge_b,
                      const char *local_abspath,
-                     svn_node_kind_t kind,
+                     struct merge_dir_baton_t *parent_baton,
+                     svn_node_kind_t node_kind,
+                     svn_wc_conflict_action_t action,
+                     svn_wc_conflict_reason_t reason,
+                     const svn_wc_conflict_description2_t *existing_conflict,
+                     svn_boolean_t notify_tc,
                      apr_pool_t *scratch_pool)
 {
+  svn_wc_context_t *wc_ctx = merge_b->ctx->wc_ctx;
+
+  if (merge_b->merge_source.ancestral
+      || merge_b->reintegrate_merge)
+    {
+      store_path(merge_b->tree_conflicted_abspaths, local_abspath);
+    }
+
+  alloc_and_store_path(&merge_b->conflicted_paths, local_abspath,
+                       merge_b->pool);
+
+
+  if (!merge_b->record_only && !merge_b->dry_run)
+    {
+       svn_wc_conflict_description2_t *conflict;
+       const svn_wc_conflict_version_t *left;
+       const svn_wc_conflict_version_t *right;
+       apr_pool_t *result_pool = parent_baton ? parent_baton->pool
+                                              : scratch_pool;
+
+      if (reason == svn_wc_conflict_reason_deleted)
+        {
+          const char *moved_to_abspath;
+
+          SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
+                                              wc_ctx, local_abspath,
+                                              scratch_pool, scratch_pool));
+
+          if (moved_to_abspath)
+            {
+              /* Local abspath itself has been moved away. If only a
+                 descendant is moved away, we call the node itself deleted */
+              reason = svn_wc_conflict_reason_moved_away;
+            }
+        }
+      else if (reason == svn_wc_conflict_reason_added)
+        {
+          const char *moved_from_abspath;
+          SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
+                                              wc_ctx, local_abspath,
+                                              scratch_pool, scratch_pool));
+          if (moved_from_abspath)
+            reason = svn_wc_conflict_reason_moved_here;
+        }
+
+      SVN_ERR(make_conflict_versions(&left, &right, local_abspath, node_kind,
+                                     &merge_b->merge_source, merge_b->target,
+                                     result_pool, scratch_pool));
+
+      /* Fix up delete of file, add of dir replacement (or other way around) */
+      if (existing_conflict != NULL && existing_conflict->src_left_version)
+          left = existing_conflict->src_left_version;
+
+      conflict = svn_wc_conflict_description_create_tree2(
+                        local_abspath, node_kind, svn_wc_operation_merge,
+                        left, right, result_pool);
+
+      conflict->action = action;
+      conflict->reason = reason;
+
+      /* May return SVN_ERR_WC_PATH_UNEXPECTED_STATUS */
+      if (existing_conflict)
+        SVN_ERR(svn_wc__del_tree_conflict(wc_ctx, local_abspath,
+                                          scratch_pool));
+
+      SVN_ERR(svn_wc__add_tree_conflict(merge_b->ctx->wc_ctx, conflict,
+                                        scratch_pool));
+
+      if (parent_baton)
+        {
+          if (! parent_baton->new_tree_conflicts)
+            parent_baton->new_tree_conflicts = apr_hash_make(result_pool);
+
+          svn_hash_sets(parent_baton->new_tree_conflicts,
+                        apr_pstrdup(result_pool, local_abspath),
+                        conflict);
+        }
+
+      /* ### TODO: Store in parent baton */
+    }
+
   /* On a replacement we currently get two tree conflicts */
-  if (merge_b->ctx->notify_func2
-      && !contains_path(merge_b->tree_conflicted_abspaths,
-                        local_abspath))
+  if (merge_b->ctx->notify_func2 && notify_tc)
     {
       svn_wc_notify_t *notify;
 
@@ -1519,18 +1395,12 @@ record_tree_conflict(merge_cmd_baton_t *
 
       notify = svn_wc_create_notify(local_abspath, svn_wc_notify_tree_conflict,
                                     scratch_pool);
-      notify->kind = kind;
+      notify->kind = node_kind;
 
       (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2, notify,
                                     scratch_pool);
     }
 
-  if (merge_b->merge_source.ancestral
-      || merge_b->reintegrate_merge)
-    {
-      store_path(merge_b->tree_conflicted_abspaths, local_abspath);
-    }
-
   return SVN_NO_ERROR;
 }
 
@@ -1547,42 +1417,8 @@ record_update_add(merge_cmd_baton_t *mer
                   svn_boolean_t notify_replaced,
                   apr_pool_t *scratch_pool)
 {
-  svn_boolean_t root_of_added_subtree = TRUE;
-
   if (merge_b->merge_source.ancestral || merge_b->reintegrate_merge)
     {
-      /* Stash the root path of any added subtrees. */
-      if (merge_b->added_abspaths == NULL)
-        {
-          /* The first added path is always a root. */
-          merge_b->added_abspaths = apr_hash_make(merge_b->pool);
-        }
-      else
-        {
-          const char *added_path_dir = svn_dirent_dirname(local_abspath,
-                                                          scratch_pool);
-
-          /* Is NOTIFY->PATH the root of an added subtree? */
-          while (strcmp(merge_b->target->abspath, added_path_dir))
-            {
-              if (contains_path(merge_b->added_abspaths, added_path_dir))
-                {
-                  root_of_added_subtree = FALSE;
-                  break;
-                }
-
-              if (svn_dirent_is_root(added_path_dir, strlen(added_path_dir)))
-                break;
-              added_path_dir = svn_dirent_dirname(added_path_dir,
-                                                  scratch_pool);
-            }
-        }
-
-      if (root_of_added_subtree)
-        {
-          store_path(merge_b->added_abspaths, local_abspath);
-        }
-
       store_path(merge_b->merged_abspaths, local_abspath);
     }
 
@@ -1656,8 +1492,7 @@ record_update_delete(merge_cmd_baton_t *
       /* Issue #4166: If a previous merge added NOTIFY_ABSPATH, but we
          are now deleting it, then remove it from the list of added
          paths. */
-      apr_hash_set(merge_b->added_abspaths, local_abspath,
-                   APR_HASH_KEY_STRING, NULL);
+      svn_hash_sets(merge_b->added_abspaths, local_abspath, NULL);
       store_path(merge_b->merged_abspaths, local_abspath);
     }
 
@@ -1670,8 +1505,8 @@ record_update_delete(merge_cmd_baton_t *
       if (!parent_db->pending_deletes)
         parent_db->pending_deletes = apr_hash_make(parent_db->pool);
 
-      apr_hash_set(parent_db->pending_deletes, dup_abspath, APR_HASH_KEY_STRING,
-                   svn_node_kind_to_word(kind));
+      svn_hash_sets(parent_db->pending_deletes, dup_abspath,
+                    svn_node_kind_to_word(kind));
     }
 
   return SVN_NO_ERROR;
@@ -1739,8 +1574,14 @@ mark_dir_edited(merge_cmd_baton_t *merge
   if (! db->shadowed)
     return SVN_NO_ERROR; /* Easy out */
 
-  if (db->tree_conflict_reason == CONFLICT_REASON_SKIP
-      || db->tree_conflict_reason == CONFLICT_REASON_SKIP_WC)
+  if (db->parent_baton
+      && db->parent_baton->delete_state
+      && db->tree_conflict_reason != CONFLICT_REASON_NONE)
+    {
+      db->parent_baton->delete_state->found_edit = TRUE;
+    }
+  else if (db->tree_conflict_reason == CONFLICT_REASON_SKIP
+           || db->tree_conflict_reason == CONFLICT_REASON_SKIP_WC)
     {
       /* open_directory() decided not to flag a tree conflict, but
          for clarity we produce a skip for this node that
@@ -1773,45 +1614,15 @@ mark_dir_edited(merge_cmd_baton_t *merge
           store_path(merge_b->skipped_abspaths, local_abspath);
         }
     }
-  else if (db->tree_conflict_reason == CONFLICT_REASON_EXCLUDED)
-    {
-      /* open_directory() decided not to flag a tree conflict, while
-         there really should be one.
-
-         Ok: The skip for the descendant will do the right thing anyway!
-       */
-
-      /* We don't register the node in merge_b->skipped_abspaths here, as the
-         node itself is not skipped: the operation on a descendant is */
-    }
   else if (db->tree_conflict_reason != CONFLICT_REASON_NONE)
     {
       /* open_directory() decided that a tree conflict should be raised */
-      const char *moved_away_abspath = NULL;
-
-      if (db->tree_conflict_reason == svn_wc_conflict_reason_deleted)
-        {
-          const char *ignored_path;
-          SVN_ERR(check_moved_away(&ignored_path, merge_b->ctx->wc_ctx,
-                                   local_abspath, scratch_pool));
-
-          if (ignored_path)
-            {
-              /* Local abspath has been moved away itself, as we only create
-                 tree conflict on the obstructing op-root.
 
-                 If only a descendant is moved away, we call the node itself
-                 deleted. */
-              db->tree_conflict_reason = svn_wc_conflict_reason_moved_away;
-              moved_away_abspath = local_abspath;
-            }
-        }
-
-      SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_dir,
-                            db->tree_conflict_action, db->tree_conflict_reason,
-                            moved_away_abspath));
-
-      SVN_ERR(record_tree_conflict(merge_b, local_abspath, svn_node_dir, scratch_pool));
+      SVN_ERR(record_tree_conflict(merge_b, local_abspath, db->parent_baton,
+                                   svn_node_dir, db->tree_conflict_action,
+                                   db->tree_conflict_reason,
+                                   NULL, TRUE,
+                                   scratch_pool));
     }
 
   return SVN_NO_ERROR;
@@ -1846,8 +1657,14 @@ mark_file_edited(merge_cmd_baton_t *merg
   if (! fb->shadowed)
     return SVN_NO_ERROR; /* Easy out */
 
-  if (fb->tree_conflict_reason == CONFLICT_REASON_SKIP
-      || fb->tree_conflict_reason == CONFLICT_REASON_SKIP_WC)
+  if (fb->parent_baton
+      && fb->parent_baton->delete_state
+      && fb->tree_conflict_reason != CONFLICT_REASON_NONE)
+    {
+      fb->parent_baton->delete_state->found_edit = TRUE;
+    }
+  else if (fb->tree_conflict_reason == CONFLICT_REASON_SKIP
+           || fb->tree_conflict_reason == CONFLICT_REASON_SKIP_WC)
     {
       /* open_directory() decided not to flag a tree conflict, but
          for clarity we produce a skip for this node that
@@ -1876,35 +1693,15 @@ mark_file_edited(merge_cmd_baton_t *merg
           store_path(merge_b->skipped_abspaths, local_abspath);
         }
     }
-  else if (fb->tree_conflict_reason != CONFLICT_REASON_NONE
-           && fb->tree_conflict_reason != CONFLICT_REASON_EXCLUDED)
+  else if (fb->tree_conflict_reason != CONFLICT_REASON_NONE)
     {
-      /* open_directory() decided that a tree conflict should be raised */
-      const char *moved_away_abspath = NULL;
-
-      if (fb->tree_conflict_reason == svn_wc_conflict_reason_deleted)
-        {
-          const char *ignored_path;
-          SVN_ERR(check_moved_away(&ignored_path, merge_b->ctx->wc_ctx,
-                                   local_abspath, scratch_pool));
-
-          if (ignored_path)
-            {
-              /* Local abspath has been moved away itself, as we only create
-                 tree conflict on the obstructing op-root.
-
-                 If only a descendant is moved away, we call the node itself
-                 deleted. */
-              fb->tree_conflict_reason = svn_wc_conflict_reason_moved_away;
-              moved_away_abspath = local_abspath;
-            }
-        }
+      /* open_file() decided that a tree conflict should be raised */
 
-      SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_file,
-                            fb->tree_conflict_action, fb->tree_conflict_reason,
-                            moved_away_abspath));
-
-      SVN_ERR(record_tree_conflict(merge_b, local_abspath, svn_node_dir, scratch_pool));
+      SVN_ERR(record_tree_conflict(merge_b, local_abspath, fb->parent_baton,
+                                   svn_node_file, fb->tree_conflict_action,
+                                   fb->tree_conflict_reason,
+                                   NULL, TRUE,
+                                   scratch_pool));
     }
 
   return SVN_NO_ERROR;
@@ -1959,13 +1756,19 @@ merge_file_opened(void **new_file_baton,
       /* Node is expected to be a file, which will be changed or deleted. */
       svn_node_kind_t kind;
       svn_boolean_t is_deleted;
+      svn_boolean_t excluded;
+      svn_depth_t parent_depth;
+
+      if (! right_source)
+        fb->tree_conflict_action = svn_wc_conflict_action_delete;
 
       {
         svn_wc_notify_state_t obstr_state;
 
-        SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &kind,
+        SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &excluded,
+                                          &kind, &parent_depth,
                                           merge_b, local_abspath,
-                                          svn_node_unknown, scratch_pool));
+                                          scratch_pool));
 
         if (obstr_state != svn_wc_notify_state_inapplicable)
           {
@@ -1991,24 +1794,15 @@ merge_file_opened(void **new_file_baton,
              Non-inheritable mergeinfo will be recorded, allowing
              future merges into non-shallow working copies to merge
              changes we missed this time around. */
-          if (pdb != NULL)
-            {
-              svn_depth_t parent_depth;
-              const char *dir_abspath = svn_dirent_dirname(local_abspath,
-                                                           scratch_pool);
-
-              SVN_ERR(svn_wc__node_get_depth(&parent_depth,
-                                             merge_b->ctx->wc_ctx,
-                                             dir_abspath, scratch_pool));
-              if (parent_depth != svn_depth_unknown &&
-                  parent_depth < svn_depth_immediates)
-                {
-                  fb->shadowed = TRUE;
-
-                  fb->tree_conflict_reason = CONFLICT_REASON_SKIP;
-                  fb->skip_reason = svn_wc_notify_state_missing;
-                  return SVN_NO_ERROR;
-                }
+          if (pdb && (excluded
+                      || (parent_depth != svn_depth_unknown &&
+                          parent_depth < svn_depth_files)))
+            {
+                fb->shadowed = TRUE;
+
+                fb->tree_conflict_reason = CONFLICT_REASON_SKIP;
+                fb->skip_reason = svn_wc_notify_state_missing;
+                return SVN_NO_ERROR;
             }
 
           if (is_deleted)
@@ -2046,33 +1840,73 @@ merge_file_opened(void **new_file_baton,
               return SVN_NO_ERROR; /* Already set a tree conflict */
             }
 
-          /* TODO: Start comparison mode to verify for delete tree conflict */
+          /* Comparison mode to verify for delete tree conflicts? */
+          if (pdb && pdb->delete_state
+              && pdb->delete_state->found_edit)
+            {
+              /* Earlier nodes found a conflict. Done. */
+              *skip = TRUE;
+            }
         }
     }
   else
     {
+      const svn_wc_conflict_description2_t *old_tc = NULL;
+
       /* The node doesn't exist pre-merge: We have an addition */
       fb->added = TRUE;
       fb->tree_conflict_action = svn_wc_conflict_action_add;
 
       if (pdb && pdb->pending_deletes
-          && contains_path(pdb->pending_deletes, local_abspath))
+          && svn_hash_gets(pdb->pending_deletes, local_abspath))
         {
           fb->add_is_replace = TRUE;
+          fb->tree_conflict_action = svn_wc_conflict_action_replace;
 
-          apr_hash_set(pdb->pending_deletes, local_abspath,
-                       APR_HASH_KEY_STRING, NULL);
+          svn_hash_sets(pdb->pending_deletes, local_abspath, NULL);
         }
 
-      if (! (merge_b->dry_run && ((pdb && pdb->added) || fb->add_is_replace)))
+      if (pdb
+          && pdb->new_tree_conflicts
+          && (old_tc = svn_hash_gets(pdb->new_tree_conflicts, local_abspath)))
+        {
+          fb->tree_conflict_action = svn_wc_conflict_action_replace;
+          fb->tree_conflict_reason = old_tc->reason;
+
+          /* Update the tree conflict to store that this is a replace */
+          SVN_ERR(record_tree_conflict(merge_b, local_abspath, pdb,
+                                       svn_node_file,
+                                       fb->tree_conflict_action,
+                                       fb->tree_conflict_reason,
+                                       old_tc, FALSE,
+                                       scratch_pool));
+
+          if (old_tc->reason == svn_wc_conflict_reason_deleted
+              || old_tc->reason == svn_wc_conflict_reason_moved_away)
+            {
+              /* Issue #3806: Incoming replacements on local deletes produce
+                 inconsistent result.
+
+                 In this specific case we can continue applying the add part
+                 of the replacement. */
+            }
+          else
+            {
+              *skip = TRUE;
+
+              return SVN_NO_ERROR;
+            }
+        }
+      else if (! (merge_b->dry_run
+                  && ((pdb && pdb->added) || fb->add_is_replace)))
         {
           svn_wc_notify_state_t obstr_state;
           svn_node_kind_t kind;
           svn_boolean_t is_deleted;
 
-          SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &kind,
+          SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, NULL,
+                                            &kind, NULL,
                                             merge_b, local_abspath,
-                                            svn_node_unknown,
                                             scratch_pool));
 
           if (obstr_state != svn_wc_notify_state_inapplicable)
@@ -2161,7 +1995,8 @@ merge_file_changed(const char *relpath,
                                       scratch_pool, scratch_pool));
 
   SVN_ERR(make_conflict_versions(&left, &right, local_abspath,
-                                 svn_node_file, &merge_b->merge_source, merge_b->target, merge_b->pool));
+                                 svn_node_file, &merge_b->merge_source, merge_b->target,
+                                 scratch_pool, scratch_pool));
 
   /* Do property merge now, if we are not going to perform a text merge */
   if ((merge_b->record_only || !left_file) && prop_changes->nelts)
@@ -2298,14 +2133,18 @@ merge_file_added(const char *relpath,
       return SVN_NO_ERROR;
     }
 
+  if ((merge_b->merge_source.ancestral || merge_b->reintegrate_merge)
+      && ( !fb->parent_baton || !fb->parent_baton->added))
+    {
+      /* Store the roots of added subtrees */
+      store_path(merge_b->added_abspaths, local_abspath);
+    }
 
   if (!merge_b->dry_run)
     {
       const char *copyfrom_url;
       svn_revnum_t copyfrom_rev;
       svn_stream_t *new_contents, *pristine_contents;
-      svn_boolean_t existing_tree_conflict;
-      svn_error_t *err;
 
       /* If this is a merge from the same repository as our
          working copy, we handle adds as add-with-history.
@@ -2331,8 +2170,7 @@ merge_file_added(const char *relpath,
           pristine_props = right_props; /* Includes last_* information */
           new_props = NULL; /* No local changes */
 
-          if (apr_hash_get(pristine_props, SVN_PROP_MERGEINFO,
-                           APR_HASH_KEY_STRING))
+          if (svn_hash_gets(pristine_props, SVN_PROP_MERGEINFO))
             {
               alloc_and_store_path(&merge_b->paths_with_new_mergeinfo,
                                    local_abspath, merge_b->pool);
@@ -2360,68 +2198,22 @@ merge_file_added(const char *relpath,
           new_props = svn_prop_array_to_hash(regular_props, scratch_pool);
 
           /* Issue #3383: We don't want mergeinfo from a foreign repository. */
-          apr_hash_set(new_props, SVN_PROP_MERGEINFO, APR_HASH_KEY_STRING,
-                       NULL);
+          svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
         }
 
-      err = svn_wc_conflicted_p3(NULL, NULL, &existing_tree_conflict,
-                                 merge_b->ctx->wc_ctx, local_abspath,
-                                 merge_b->pool);
+      /* Do everything like if we had called 'svn cp PATH1 PATH2'. */
+      SVN_ERR(svn_wc_add_repos_file4(merge_b->ctx->wc_ctx,
+                                      local_abspath,
+                                      pristine_contents,
+                                      new_contents,
+                                      pristine_props, new_props,
+                                      copyfrom_url, copyfrom_rev,
+                                      merge_b->ctx->cancel_func,
+                                      merge_b->ctx->cancel_baton,
+                                      scratch_pool));
 
-      if (err)
-        {
-          if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
-            return svn_error_trace(err);
-
-          svn_error_clear(err);
-          existing_tree_conflict = FALSE;
-        }
-
-      if (existing_tree_conflict)
-        {
-          svn_boolean_t moved_here;
-          svn_wc_conflict_reason_t reason;
-
-          /* Possibly collapse the existing conflict into a 'replace'
-           * tree conflict. The conflict reason is 'added' because
-           * the now-deleted tree conflict victim must have been
-           * added in the history of the merge target. */
-          SVN_ERR(check_moved_here(&moved_here, merge_b->ctx->wc_ctx,
-                                   local_abspath, scratch_pool));
-          reason = moved_here ? svn_wc_conflict_reason_moved_here
-                              : svn_wc_conflict_reason_added;
-          SVN_ERR(tree_conflict_on_add(merge_b, local_abspath,
-                                       svn_node_file,
-                                       svn_wc_conflict_action_add,
-                                       reason));
-
-          SVN_ERR(record_tree_conflict(merge_b, local_abspath, svn_node_file,
-                                       scratch_pool));
-          return SVN_NO_ERROR;
-        }
-      else
-        {
-          /* Since 'mine' doesn't exist, and this is
-             'merge_file_added', I hope it's safe to assume that
-             'older' is empty, and 'yours' is the full file.  Merely
-             copying 'yours' to 'mine', isn't enough; we need to get
-             the whole text-base and props installed too, just as if
-             we had called 'svn cp wc wc'. */
-          SVN_ERR(svn_wc_add_repos_file4(merge_b->ctx->wc_ctx,
-                                         local_abspath,
-                                         pristine_contents,
-                                         new_contents,
-                                         pristine_props, new_props,
-                                         copyfrom_url, copyfrom_rev,
-                                         merge_b->ctx->cancel_func,
-                                         merge_b->ctx->cancel_baton,
-                                         scratch_pool));
-        }
-    }
-  else /* dry_run */
-    {
-      /* ### Should we do this?
-      store_path(merge_b->dry_run_added, local_abspath); */
+      /* Caller must call svn_sleep_for_timestamps() */
+      *merge_b->use_sleep = TRUE;
     }
 
   SVN_ERR(record_update_add(merge_b, local_abspath, svn_node_file,
@@ -2553,21 +2345,45 @@ merge_file_deleted(const char *relpath,
     }
 
   /* If the files are identical, attempt deletion */
-  SVN_ERR(files_same_p(&same, left_file, left_props,
-                       local_abspath, merge_b->ctx->wc_ctx,
-                       scratch_pool));
-  if (same || merge_b->force_delete || merge_b->record_only /* ### why? */)
-    {
-      /* Passing NULL for the notify_func and notify_baton because
-         repos_diff.c:delete_entry() will do it for us. */
-      SVN_ERR(svn_client__wc_delete(local_abspath, TRUE,
-                                    merge_b->dry_run, FALSE, NULL, NULL,
-                                    merge_b->ctx, scratch_pool));
+  if (merge_b->force_delete)
+    same = TRUE;
+  else
+    SVN_ERR(files_same_p(&same, left_file, left_props,
+                         local_abspath, merge_b->ctx->wc_ctx,
+                         scratch_pool));
+
+  if (fb->parent_baton
+      && fb->parent_baton->delete_state)
+    {
+      if (same)
+        {
+          /* Note that we checked this file */
+          store_path(fb->parent_baton->delete_state->compared_abspaths,
+                     local_abspath);
+        }
+      else
+        {
+          /* We found some modification. Parent should raise a tree conflict */
+          fb->parent_baton->delete_state->found_edit = TRUE;
+        }
+
+      return SVN_NO_ERROR;
+    }
+  else if (same)
+    {
+      if (!merge_b->dry_run)
+        SVN_ERR(svn_wc_delete4(merge_b->ctx->wc_ctx, local_abspath,
+                               FALSE /* keep_local */, FALSE /* unversioned */,
+                               merge_b->ctx->cancel_func,
+                               merge_b->ctx->cancel_baton,
+                               NULL, NULL /* no notify */,
+                               scratch_pool));
 
       /* Record that we might have deleted mergeinfo */
       alloc_and_store_path(&merge_b->paths_with_deleted_mergeinfo,
                            local_abspath, merge_b->pool);
 
+      /* And notify the deletion */
       SVN_ERR(record_update_delete(merge_b, fb->parent_baton, local_abspath,
                                    svn_node_file, scratch_pool));
     }
@@ -2575,14 +2391,14 @@ merge_file_deleted(const char *relpath,
     {
       /* The files differ, so raise a conflict instead of deleting */
 
-      /* This might be use case 5 described in the paper attached to issue
+      /* This is use case 5 described in the paper attached to issue
        * #2282.  See also notes/tree-conflicts/detection.txt
        */
-      SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_file,
-                            svn_wc_conflict_action_delete,
-                            svn_wc_conflict_reason_edited, NULL));
-
-      SVN_ERR(record_tree_conflict(merge_b, local_abspath, svn_node_dir,
+      SVN_ERR(record_tree_conflict(merge_b, local_abspath, fb->parent_baton,
+                                   svn_node_file,
+                                   svn_wc_conflict_action_delete,
+                                   svn_wc_conflict_reason_edited,
+                                   NULL, TRUE,
                                    scratch_pool));
     }
   
@@ -2630,9 +2446,6 @@ merge_dir_opened(void **new_dir_baton,
 
   *new_dir_baton = db;
 
-  if (! right_source)
-    *skip_children = TRUE; /* ### Compatibility with walk_children = FALSE */
-
   if (pdb)
     {
       db->parent_baton = pdb;
@@ -2651,13 +2464,18 @@ merge_dir_opened(void **new_dir_baton,
       /* Node is expected to be a directory. */
       svn_node_kind_t kind;
       svn_boolean_t is_deleted;
+      svn_boolean_t excluded;
+      svn_depth_t parent_depth;
+
+      if (! right_source)
+          db->tree_conflict_action = svn_wc_conflict_action_delete;
 
       /* Check for an obstructed or missing node on disk. */
       {
         svn_wc_notify_state_t obstr_state;
-        SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &kind,
+        SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &excluded,
+                                          &kind, &parent_depth,
                                           merge_b, local_abspath,
-                                          svn_node_unknown,
                                           scratch_pool));
 
         if (obstr_state != svn_wc_notify_state_inapplicable)
@@ -2681,6 +2499,14 @@ merge_dir_opened(void **new_dir_baton,
 
             db->tree_conflict_reason = CONFLICT_REASON_SKIP;
             db->skip_reason = obstr_state;
+
+            if (! right_source)
+              {
+                *skip = *skip_children = TRUE;
+                SVN_ERR(mark_dir_edited(merge_b, db, local_abspath,
+                                        scratch_pool));
+              }
+
             return SVN_NO_ERROR;
           }
 
@@ -2700,25 +2526,16 @@ merge_dir_opened(void **new_dir_baton,
              Non-inheritable mergeinfo will be recorded, allowing
              future merges into non-shallow working copies to merge
              changes we missed this time around. */
-          if (pdb != NULL)
+          if (pdb && (excluded
+                      || (parent_depth != svn_depth_unknown &&
+                          parent_depth < svn_depth_immediates)))
             {
-              svn_depth_t parent_depth;
-              const char *dir_abspath = svn_dirent_dirname(local_abspath,
-                                                           scratch_pool);
-
-              SVN_ERR(svn_wc__node_get_depth(&parent_depth,
-                                             merge_b->ctx->wc_ctx,
-                                             dir_abspath, scratch_pool));
-              if (parent_depth != svn_depth_unknown &&
-                  parent_depth < svn_depth_immediates)
-                {
-                  db->shadowed = TRUE;
+              db->shadowed = TRUE;
 
-                  db->tree_conflict_reason = CONFLICT_REASON_SKIP;
-                  db->skip_reason = svn_wc_notify_state_missing;
+              db->tree_conflict_reason = CONFLICT_REASON_SKIP;
+              db->skip_reason = svn_wc_notify_state_missing;
 
-                  return SVN_NO_ERROR;
-                }
+              return SVN_NO_ERROR;
             }
 
           if (is_deleted)
@@ -2756,36 +2573,94 @@ merge_dir_opened(void **new_dir_baton,
 
           if (db->shadowed)
             {
+              *skip_children = TRUE;
               return SVN_NO_ERROR; /* Already set a tree conflict */
             }
 
-          /* TODO: Start comparison mode to verify for delete tree conflict */
+          db->delete_state = (pdb != NULL) ? pdb->delete_state : NULL;
+
+          if (db->delete_state && db->delete_state->found_edit)
+            {
+              /* A sibling found a conflict. Done. */
+              *skip = TRUE;
+              *skip_children = TRUE;
+            }
+          else if (merge_b->force_delete)
+            {
+              /* No comparison necessary */
+              *skip_children = TRUE;
+            }
+          else if (! db->delete_state)
+            {
+              /* Start descendant comparison */
+              db->delete_state = apr_pcalloc(db->pool,
+                                             sizeof(*db->delete_state));
+
+              db->delete_state->del_root = db;
+              db->delete_state->compared_abspaths = apr_hash_make(db->pool);
+            }
         }
     }
   else
     {
+      const svn_wc_conflict_description2_t *old_tc = NULL;
+
       /* The node doesn't exist pre-merge: We have an addition */
       db->added = TRUE;
       db->tree_conflict_action = svn_wc_conflict_action_add;
 
       if (pdb && pdb->pending_deletes
-          && contains_path(pdb->pending_deletes, local_abspath))
+          && svn_hash_gets(pdb->pending_deletes, local_abspath))
+        {
+          db->add_is_replace = TRUE;
+          db->tree_conflict_action = svn_wc_conflict_action_replace;
+
+          svn_hash_sets(pdb->pending_deletes, local_abspath, NULL);
+        }
+
+      if (pdb
+          && pdb->new_tree_conflicts
+          && (old_tc = svn_hash_gets(pdb->new_tree_conflicts, local_abspath)))
         {
-          db->add_is_replace = TRUE;
+          db->tree_conflict_action = svn_wc_conflict_action_replace;
+          db->tree_conflict_reason = old_tc->reason;
 
-          apr_hash_set(pdb->pending_deletes, local_abspath,
-                       APR_HASH_KEY_STRING, NULL);
-        }
+          if (old_tc->reason == svn_wc_conflict_reason_deleted
+             || old_tc->reason == svn_wc_conflict_reason_moved_away)
+            {
+              /* Issue #3806: Incoming replacements on local deletes produce
+                 inconsistent result.
+
+                 In this specific case we can continue applying the add part
+                 of the replacement. */
+            }
+          else
+            {
+              *skip = TRUE;
+              *skip_children = TRUE;
+
+              /* Update the tree conflict to store that this is a replace */
+              SVN_ERR(record_tree_conflict(merge_b, local_abspath, pdb,
+                                           svn_node_dir,
+                                           db->tree_conflict_action,
+                                           db->tree_conflict_reason,
+                                           old_tc, FALSE,
+                                           scratch_pool));
 
-      if (! (merge_b->dry_run && ((pdb && pdb->added) || db->add_is_replace)))
+              return SVN_NO_ERROR;
+            }
+        }
+      
+      if (! (merge_b->dry_run
+             && ((pdb && pdb->added) || db->add_is_replace)))
         {
           svn_wc_notify_state_t obstr_state;
           svn_node_kind_t kind;
           svn_boolean_t is_deleted;
 
-          SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &kind,
+          SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, NULL,
+                                            &kind, NULL,
                                             merge_b, local_abspath,
-                                            svn_node_unknown,
                                             scratch_pool));
 
           /* In this case of adding a directory, we have an exception to the
@@ -2844,6 +2719,19 @@ merge_dir_opened(void **new_dir_baton,
             SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT,
                                     scratch_pool));
 
+          if (old_tc)
+            {
+              /* svn_wc_add4 and svn_wc_add_from_disk2 can't add a node
+                 over an existing tree conflict */
+
+              /* ### These functions should take some tree conflict argument
+                     and allow overwriting the tc when one is passed */
+
+              SVN_ERR(svn_wc__del_tree_conflict(merge_b->ctx->wc_ctx,
+                                                local_abspath,
+                                                scratch_pool));
+            }
+
           if (merge_b->same_repos)
             {
               const char *original_url;
@@ -2876,6 +2764,17 @@ merge_dir_opened(void **new_dir_baton,
                                             NULL, NULL /* no notify! */,
                                             scratch_pool));
             }
+
+          if (old_tc != NULL)
+            {
+              /* ### Should be atomic with svn_wc_add(4|_from_disk2)() */
+              SVN_ERR(record_tree_conflict(merge_b, local_abspath, pdb,
+                                           svn_node_dir,
+                                           db->tree_conflict_action,
+                                           db->tree_conflict_reason,
+                                           old_tc, FALSE,
+                                           scratch_pool));
+            }
         }
 
       if (! db->shadowed && !merge_b->record_only)
@@ -2939,7 +2838,8 @@ merge_dir_changed(const char *relpath,
 
       SVN_ERR(make_conflict_versions(&left, &right, local_abspath,
                                      svn_node_dir, &merge_b->merge_source,
-                                     merge_b->target, merge_b->pool));
+                                     merge_b->target,
+                                     scratch_pool, scratch_pool));
 
       SVN_ERR(svn_wc_merge_props3(&prop_state, ctx->wc_ctx, local_abspath,
                                   left, right,
@@ -3015,6 +2915,13 @@ merge_dir_added(const char *relpath,
                  && ! merge_b->record_only /* Skip details from merge_open_dir() */
                  );
 
+  if ((merge_b->merge_source.ancestral || merge_b->reintegrate_merge)
+      && ( !db->parent_baton || !db->parent_baton->added))
+    {
+      /* Store the roots of added subtrees */
+      store_path(merge_b->added_abspaths, local_abspath);
+    }
+
   if (merge_b->same_repos)
     {
       /* When the directory was added in merge_dir_added() we didn't update its
@@ -3058,14 +2965,11 @@ merge_dir_added(const char *relpath,
                                                 scratch_pool));
         }
 
-      if (apr_hash_get(new_pristine_props, SVN_PROP_MERGEINFO,
-                       APR_HASH_KEY_STRING))
+      if (svn_hash_gets(new_pristine_props, SVN_PROP_MERGEINFO))
         {
           alloc_and_store_path(&merge_b->paths_with_new_mergeinfo,
                                local_abspath, merge_b->pool);
         }
-
-      return SVN_NO_ERROR;
     }
   else
     {
@@ -3079,7 +2983,7 @@ merge_dir_added(const char *relpath,
 
       new_props = svn_prop_array_to_hash(regular_props, scratch_pool);
 
-      apr_hash_set(new_props, SVN_PROP_MERGEINFO, APR_HASH_KEY_STRING, NULL);
+      svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
 
       /* ### What is the easiest way to set new_props on LOCAL_ABSPATH?
 
@@ -3103,6 +3007,31 @@ merge_dir_added(const char *relpath,
   return SVN_NO_ERROR;
 }
 
+/* Helper for merge_dir_deleted. Implement svn_wc_status_func4_t */
+static svn_error_t *
+verify_touched_by_del_check(void *baton,
+                            const char *local_abspath,
+                            const svn_wc_status3_t *status,
+                            apr_pool_t *scratch_pool)
+{
+  struct dir_delete_baton_t *delb = baton;
+
+  if (svn_hash_gets(delb->compared_abspaths, local_abspath))
+    return SVN_NO_ERROR;
+
+  switch (status->node_status)
+    {
+      case svn_wc_status_deleted:
+      case svn_wc_status_ignored:
+      case svn_wc_status_none:
+        return SVN_NO_ERROR;
+
+      default:
+        delb->found_edit = TRUE;
+        return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
+    }
+}
+
 /* An svn_diff_tree_processor_t function.
  *
  * Called after merge_dir_opened() when a node existed only in the left source.
@@ -3110,9 +3039,6 @@ merge_dir_added(const char *relpath,
  * After the merge_dir_opened() but before the call to this merge_dir_deleted()
  * function all descendants that existed in left_source will have been deleted.
  *
- * ### By default the diff drivers report all descendants of a delete,
- * ### but this feature is currently disabled by a SKIP on merge_dir_opened()
- *
  * If this node is replaced, an _opened() followed by a matching _add() will
  * be invoked after this function.
  */
@@ -3128,7 +3054,9 @@ merge_dir_deleted(const char *relpath,
   struct merge_dir_baton_t *db = dir_baton;
   const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
                                               relpath, scratch_pool);
-  svn_error_t *err;
+  struct dir_delete_baton_t *delb;
+  svn_boolean_t same;
+  apr_hash_t *working_props;
 
   SVN_ERR(handle_pending_notifications(merge_b, db, scratch_pool));
   SVN_ERR(mark_dir_edited(merge_b, db, local_abspath, scratch_pool));
@@ -3152,38 +3080,119 @@ merge_dir_deleted(const char *relpath,
       return SVN_NO_ERROR;
     }
 
-  /* ### TODO: Before deleting, we should ensure that this dir
-     tree is equal to the one we're being asked to delete.
-     If not, mark this directory as a tree conflict victim,
-     because this could be use case 5 as described in
-     notes/tree-conflicts/detection.txt.
-   */
+  SVN_ERR(svn_wc_prop_list2(&working_props,
+                            merge_b->ctx->wc_ctx, local_abspath,
+                            scratch_pool, scratch_pool));
 
-  /* Passing NULL for the notify_func and notify_baton because
-     repos_diff.c:delete_entry() will do it for us. */
-  err = svn_client__wc_delete(local_abspath, merge_b->force_delete,
-                              merge_b->dry_run, FALSE,
-                              NULL, NULL,
-                              merge_b->ctx, scratch_pool);
-  if (err)
+  if (merge_b->force_delete)
+    same = TRUE;
+  else
     {
-      svn_error_clear(err);
+      /* Compare the properties */
+      SVN_ERR(properties_same_p(&same, left_props, working_props,
+                                scratch_pool));
+    }
+
+  delb = db->delete_state;
+  assert(delb != NULL);
+
+  if (! same)
+    {
+      delb->found_edit = TRUE;
+    }
+  else
+    {
+      store_path(delb->compared_abspaths, local_abspath);
+    }
+
+  if (delb->del_root != db)
+    return SVN_NO_ERROR;
+
+  if (delb->found_edit)
+    same = FALSE;
+  else if (merge_b->force_delete)
+    same = TRUE;
+  else
+    {
+      apr_array_header_t *ignores;
+      svn_error_t *err;
+      same = TRUE;
 
+      SVN_ERR(svn_wc_get_default_ignores(&ignores, merge_b->ctx->config,
+                                         scratch_pool));
+
+      /* None of the descendants was modified, but maybe there are
+         descendants we haven't walked?
+
+         Note that we aren't interested in changes, as we already verified
+         changes in the paths touched by the merge. And the existance of
+         other paths is enough to mark the directory edited */
+      err = svn_wc_walk_status(merge_b->ctx->wc_ctx, local_abspath,
+                               svn_depth_infinity, TRUE /* get-all */,
+                               FALSE /* no-ignore */,
+                               TRUE /* ignore-text-mods */, ignores,
+                               verify_touched_by_del_check, delb,
+                               merge_b->ctx->cancel_func,
+                               merge_b->ctx->cancel_baton,
+                               scratch_pool);
+
+      if (err)
+        {
+          if (err->apr_err != SVN_ERR_CEASE_INVOCATION)
+            return svn_error_trace(err);
+
+          svn_error_clear(err);
+        }
+
+      same = ! delb->found_edit;
+    }
+
+  if (same && !merge_b->dry_run)
+    {
+      svn_error_t *err;
+
+      err = svn_wc_delete4(merge_b->ctx->wc_ctx, local_abspath,
+                           FALSE /* keep_local */, FALSE /* unversioned */,
+                           merge_b->ctx->cancel_func,
+                           merge_b->ctx->cancel_baton,
+                           NULL, NULL /* no notify */,
+                           scratch_pool);
+
+      if (err)
+        {
+          if (err->apr_err != SVN_ERR_WC_LEFT_LOCAL_MOD)
+            return svn_error_trace(err);
+
+          svn_error_clear(err);
+          same = FALSE;
+        }
+    }
+
+  if (! same)
+    {
       /* If the attempt to delete an existing directory failed,
        * the directory has local modifications (e.g. locally added
        * files, or property changes). Flag a tree conflict. */
-      SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_dir,
-                            svn_wc_conflict_action_delete,
-                            svn_wc_conflict_reason_edited, NULL));
 
-      SVN_ERR(record_tree_conflict(merge_b, local_abspath, svn_node_dir,
+      /* This handles use case 5 described in the paper attached to issue
+       * #2282.  See also notes/tree-conflicts/detection.txt
+       */
+      SVN_ERR(record_tree_conflict(merge_b, local_abspath, db->parent_baton,
+                                   svn_node_dir,
+                                   svn_wc_conflict_action_delete,
+                                   svn_wc_conflict_reason_edited,
+                                   NULL, TRUE,
                                    scratch_pool));
     }
   else
     {
       /* Record that we might have deleted mergeinfo */
-      alloc_and_store_path(&merge_b->paths_with_deleted_mergeinfo,
-                           local_abspath, merge_b->pool);
+      if (working_props
+          && svn_hash_gets(working_props, SVN_PROP_MERGEINFO))
+        {
+          alloc_and_store_path(&merge_b->paths_with_deleted_mergeinfo,
+                               local_abspath, merge_b->pool);
+        }
 
       SVN_ERR(record_update_delete(merge_b, db->parent_baton, local_abspath,
                                    svn_node_dir, scratch_pool));
@@ -3240,8 +3249,9 @@ merge_node_absent(const char *relpath,
   const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
                                               relpath, scratch_pool);
 
-  record_skip(merge_b, local_abspath, svn_node_unknown,
-              svn_wc_notify_skip, svn_wc_notify_state_missing, scratch_pool);
+  SVN_ERR(record_skip(merge_b, local_abspath, svn_node_unknown,
+                      svn_wc_notify_skip, svn_wc_notify_state_missing,
+                      scratch_pool));
 
   return SVN_NO_ERROR;
 }
@@ -3280,6 +3290,125 @@ find_nearest_ancestor(const apr_array_he
   return NULL;
 }
 
+/* Find the highest level path in a merge target (possibly the merge target
+   itself) to use in a merge notification header.
+
+   Return the svn_client__merge_path_t * representing the most distant
+   ancestor in CHILDREN_WITH_MERGEINFO of LOCAL_ABSPATH where said
+   ancestor's first remaining ranges element (per the REMAINING_RANGES
+   member of the ancestor) intersect with the first remaining ranges element
+   for every intermediate ancestor svn_client__merge_path_t * of
+   LOCAL_ABSPATH.  If no such ancestor is found return NULL.
+
+   If the remaining ranges of the elements in CHILDREN_WITH_MERGEINFO
+   represent a forward merge, then set *START to the oldest revision found
+   in any of the intersecting ancestors and *END to the youngest revision
+   found.  If the remaining ranges of the elements in CHILDREN_WITH_MERGEINFO
+   represent a reverse merge, then set *START to the youngest revision
+   found and *END to the oldest revision found.  If no ancestors are found
+   then set *START and *END to SVN_INVALID_REVNUM.
+
+   If PATH_IS_OWN_ANCESTOR is TRUE then a child in CHILDREN_WITH_MERGEINFO
+   where child->abspath == PATH is considered PATH's ancestor.  If FALSE,
+   then child->abspath must be a proper ancestor of PATH.
+
+   See the CHILDREN_WITH_MERGEINFO ARRAY global comment for more
+   information. */
+static svn_client__merge_path_t *
+find_nearest_ancestor_with_intersecting_ranges(
+  svn_revnum_t *start,
+  svn_revnum_t *end,
+  const apr_array_header_t *children_with_mergeinfo,
+  svn_boolean_t path_is_own_ancestor,
+  const char *local_abspath)
+{
+  int i;
+  svn_client__merge_path_t *nearest_ancestor = NULL;
+
+  *start = SVN_INVALID_REVNUM;
+  *end = SVN_INVALID_REVNUM;
+
+  SVN_ERR_ASSERT_NO_RETURN(children_with_mergeinfo != NULL);
+
+  for (i = children_with_mergeinfo->nelts - 1; i >= 0 ; i--)
+    {
+      svn_client__merge_path_t *child =
+        APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
+
+      if (svn_dirent_is_ancestor(child->abspath, local_abspath)
+          && (path_is_own_ancestor
+              || strcmp(child->abspath, local_abspath) != 0))
+        {
+          if (nearest_ancestor == NULL)
+            {
+              /* Found an ancestor. */
+              nearest_ancestor = child;
+
+              if (child->remaining_ranges)
+                {
+                  svn_merge_range_t *r1 = APR_ARRAY_IDX(
+                    child->remaining_ranges, 0, svn_merge_range_t *);
+                  *start = r1->start;
+                  *end = r1->end;
+                }
+              else
+                {
+                  /* If CHILD->REMAINING_RANGES is null then LOCAL_ABSPATH
+                     is inside an absent subtree in the merge target. */
+                  *start = SVN_INVALID_REVNUM;
+                  *end = SVN_INVALID_REVNUM;
+                  break;
+                }
+            }
+          else
+            {
+              /* We'e found another ancestor for LOCAL_ABSPATH.  Do its
+                 first remaining range intersect with the previously
+                 found ancestor? */
+              svn_merge_range_t *r1 =
+                APR_ARRAY_IDX(nearest_ancestor->remaining_ranges, 0,
+                              svn_merge_range_t *);
+              svn_merge_range_t *r2 =
+                APR_ARRAY_IDX(child->remaining_ranges, 0,
+                              svn_merge_range_t *);
+
+              if (r1 && r2)
+                {
+                  svn_merge_range_t range1;
+                  svn_merge_range_t range2;
+                  svn_boolean_t reverse_merge = r1->start > r2->end;
+
+                  /* Flip endpoints if this is a reverse merge. */
+                  if (reverse_merge)
+                    {
+                      range1.start = r1->end;
+                      range1.end = r1->start;
+                      range2.start = r2->end;
+                      range2.end = r2->start;
+                    }
+                  else
+                    {
+                      range1.start = r1->start;
+                      range1.end = r1->end;
+                      range2.start = r2->start;
+                      range2.end = r2->end;
+                    }
+
+                  if (range1.start < range2.end && range2.start < range1.end)
+                    {
+                      *start = reverse_merge ?
+                        MAX(r1->start, r2->start) : MIN(r1->start, r2->start);
+                      *end = reverse_merge ?
+                        MIN(r1->end, r2->end) : MAX(r1->end, r2->end);
+                      nearest_ancestor = child;
+                    }
+                }
+            }
+        }
+    }
+  return nearest_ancestor;
+}
+
 /* Notify that we're starting to record mergeinfo for the merge of the
  * revision range RANGE into TARGET_ABSPATH.  RANGE should be null if the
  * merge sources are not from the same URL.
@@ -3346,19 +3475,15 @@ notify_merge_begin(merge_cmd_baton_t *me
                    apr_pool_t *scratch_pool)
 {
   svn_wc_notify_t *notify;
-  svn_merge_range_t *n_range = NULL;
+  svn_merge_range_t n_range =
+    {SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, TRUE};
   const char *notify_abspath;
 
-  if (merge_b->notified_merge_begin
-      || ! merge_b->ctx->notify_func2)
+  if (! merge_b->ctx->notify_func2)
     return SVN_NO_ERROR;
 
-  notify_abspath = merge_b->target->abspath;
-
   /* If our merge sources are ancestors of one another... */
-  /* single file merge updates current_range */
-  if (merge_b->merge_source.ancestral
-      && ! merge_b->is_single_file_merge)
+  if (merge_b->merge_source.ancestral)
     {
       const svn_client__merge_path_t *child;
       /* Find NOTIFY->PATH's nearest ancestor in
@@ -3377,38 +3502,49 @@ notify_merge_begin(merge_cmd_baton_t *me
            D    PARENT/CHILD
       */
 
-      child = find_nearest_ancestor(merge_b->children_with_mergeinfo,
-                                    ! delete_action, local_abspath);
+      child = find_nearest_ancestor_with_intersecting_ranges(
+        &(n_range.start), &(n_range.end),
+        merge_b->notify_begin.nodes_with_mergeinfo,
+        ! delete_action, local_abspath);
+
+      if (!child && delete_action)
+        {
+          /* Triggered by file replace in single-file-merge */
+          child = find_nearest_ancestor(merge_b->notify_begin.nodes_with_mergeinfo,
+                                        TRUE, local_abspath);
+        }
 
       assert(child != NULL); /* Should always find the merge anchor */
 
       if (! child)
         return SVN_NO_ERROR;
 
-      if (merge_b->cur_ancestor_abspath != NULL
-          && strcmp(child->abspath, merge_b->cur_ancestor_abspath) == 0)
+      if (merge_b->notify_begin.last_abspath != NULL
+          && strcmp(child->abspath, merge_b->notify_begin.last_abspath) == 0)
         {
           /* Don't notify the same merge again */
           return SVN_NO_ERROR;
         }
 
-      merge_b->cur_ancestor_abspath = child->abspath;
+      merge_b->notify_begin.last_abspath = child->abspath;
 
-      if (child->absent || child->remaining_ranges->nelts == 0)
+      if (child->absent || child->remaining_ranges->nelts == 0
+          || !SVN_IS_VALID_REVNUM(n_range.start))
         {
           /* No valid information for an header */
           return SVN_NO_ERROR;
         }
 
       notify_abspath = child->abspath;
-      n_range = APR_ARRAY_IDX(child->remaining_ranges, 0, svn_merge_range_t *);
     }
-  else if (merge_b->merge_source.ancestral)
+  else
     {
-      merge_b->notified_merge_begin = TRUE;
+      if (merge_b->notify_begin.last_abspath)
+        return SVN_NO_ERROR; /* already notified */
 
-      if (merge_b->current_range)
-        n_range = svn_merge_range_dup(merge_b->current_range, scratch_pool);
+      notify_abspath = merge_b->target->abspath;
+      /* Store something in last_abspath. Any value would do */
+      merge_b->notify_begin.last_abspath = merge_b->target->abspath;
     }
 
   notify = svn_wc_create_notify(notify_abspath,
@@ -3416,7 +3552,11 @@ notify_merge_begin(merge_cmd_baton_t *me
                                       ? svn_wc_notify_merge_begin
                                       : svn_wc_notify_foreign_merge_begin,
                                 scratch_pool);
-  notify->merge_range = n_range;
+
+  if (SVN_IS_VALID_REVNUM(n_range.start))
+    notify->merge_range = &n_range;
+  else
+    notify->merge_range = NULL;
 
   (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2, notify,
                                 scratch_pool);
@@ -4225,9 +4365,8 @@ filter_merged_revisions(svn_client__merg
                                             result_pool,
                                             scratch_pool));
 
-          target_implicit_rangelist = apr_hash_get(child->implicit_mergeinfo,
-                                                   mergeinfo_path,
-                                                   APR_HASH_KEY_STRING);
+          target_implicit_rangelist = svn_hash_gets(child->implicit_mergeinfo,
+                                                    mergeinfo_path);
 
           if (target_implicit_rangelist)
             SVN_ERR(svn_rangelist_intersect(&implicit_rangelist,
@@ -4311,9 +4450,8 @@ filter_merged_revisions(svn_client__merg
                                             result_pool,
                                             scratch_pool));
 
-          target_implicit_rangelist = apr_hash_get(child->implicit_mergeinfo,
-                                                   mergeinfo_path,
-                                                   APR_HASH_KEY_STRING);
+          target_implicit_rangelist = svn_hash_gets(child->implicit_mergeinfo,
+                                                    mergeinfo_path);
           if (target_implicit_rangelist)
             SVN_ERR(svn_rangelist_remove(&(child->remaining_ranges),
                                          target_implicit_rangelist,
@@ -4408,8 +4546,7 @@ calculate_remaining_ranges(svn_client__m
      ### [JAF] In fact, that may still leave some ranges that lie entirely
      outside the range of SOURCE; it seems we don't care about that.  */
   if (target_mergeinfo)
-    target_rangelist = apr_hash_get(target_mergeinfo, mergeinfo_path,
-                                    APR_HASH_KEY_STRING);
+    target_rangelist = svn_hash_gets(target_mergeinfo, mergeinfo_path);
   else
     target_rangelist = NULL;
   if (implicit_src_gap && target_rangelist)
@@ -4458,7 +4595,7 @@ calculate_remaining_ranges(svn_client__m
      So in the name of user friendliness, return an error suggesting a helpful
      course of action.
   */
-  SVN_ERR(svn_wc__node_get_base(&child_base_revision, NULL, NULL, NULL,
+  SVN_ERR(svn_wc__node_get_base(&child_base_revision, NULL, NULL, NULL, NULL,
                                 ctx->wc_ctx, child->abspath,
                                 scratch_pool, scratch_pool));
   /* If CHILD has no base revision then it hasn't been committed yet, so it
@@ -4560,9 +4697,7 @@ find_gaps_in_merge_source_history(svn_re
                                                ra_session,
                                                ctx, scratch_pool));
 
-  rangelist = apr_hash_get(implicit_src_mergeinfo,
-                           merge_src_fspath,
-                           APR_HASH_KEY_STRING);
+  rangelist = svn_hash_gets(implicit_src_mergeinfo, merge_src_fspath);
 
   if (!rangelist) /* ### Can we ever not find a rangelist? */
     return SVN_NO_ERROR;
@@ -4999,7 +5134,7 @@ update_wc_mergeinfo(svn_mergeinfo_catalo
       fspath = svn_fspath__join(source_fspath,
                                 local_abspath_rel_to_target,
                                 iterpool);
-      rangelist = apr_hash_get(mergeinfo, fspath, APR_HASH_KEY_STRING);
+      rangelist = svn_hash_gets(mergeinfo, fspath);
       if (rangelist == NULL)
         rangelist = apr_array_make(iterpool, 0, sizeof(svn_merge_range_t *));
 
@@ -5016,7 +5151,7 @@ update_wc_mergeinfo(svn_mergeinfo_catalo
           SVN_ERR(svn_rangelist_merge2(rangelist, ranges, iterpool, iterpool));
         }
       /* Update the mergeinfo by adjusting the path's rangelist. */
-      apr_hash_set(mergeinfo, fspath, APR_HASH_KEY_STRING, rangelist);
+      svn_hash_sets(mergeinfo, fspath, rangelist);
 
       if (is_rollback && apr_hash_count(mergeinfo) == 0)
         mergeinfo = NULL;
@@ -5026,16 +5161,15 @@ update_wc_mergeinfo(svn_mergeinfo_catalo
       if (result_catalog)
         {
           svn_mergeinfo_t existing_mergeinfo =
-            apr_hash_get(result_catalog, local_abspath, APR_HASH_KEY_STRING);
+            svn_hash_gets(result_catalog, local_abspath);
           apr_pool_t *result_catalog_pool = apr_hash_pool_get(result_catalog);
 
           if (existing_mergeinfo)
             SVN_ERR(svn_mergeinfo_merge2(mergeinfo, existing_mergeinfo,
                                          result_catalog_pool, scratch_pool));
-          apr_hash_set(result_catalog,
-                       apr_pstrdup(result_catalog_pool, local_abspath),
-                       APR_HASH_KEY_STRING,
-                       svn_mergeinfo_dup(mergeinfo, result_catalog_pool));
+          svn_hash_sets(result_catalog,
+                        apr_pstrdup(result_catalog_pool, local_abspath),
+                        svn_mergeinfo_dup(mergeinfo, result_catalog_pool));
         }
       else
         {
@@ -5108,8 +5242,9 @@ record_skips_in_mergeinfo(const char *me
       /* Before we override, make sure this is a versioned path, it might
          be an external or missing from disk due to authz restrictions. */
       SVN_ERR(perform_obstruction_check(&obstruction_state, NULL, NULL,
+                                        NULL, NULL,
                                         merge_b, skipped_abspath,
-                                        svn_node_unknown, iterpool));
+                                        iterpool));
       if (obstruction_state == svn_wc_notify_state_obstructed
           || obstruction_state == svn_wc_notify_state_missing)
         continue;
@@ -5125,10 +5260,9 @@ record_skips_in_mergeinfo(const char *me
          ### inherit any mergeinfo from a parent, but if it does
          ### we need to account for that.  See issue #3440
          ### http://subversion.tigris.org/issues/show_bug.cgi?id=3440. */
-      apr_hash_set(merges, skipped_abspath,
-                   APR_HASH_KEY_STRING,
-                   apr_array_make(scratch_pool, 0,
-                                  sizeof(svn_merge_range_t *)));
+      svn_hash_sets(merges, skipped_abspath,
+                    apr_array_make(scratch_pool, 0,
+                                   sizeof(svn_merge_range_t *)));
 
       /* if (nbr_skips < notify_b->nbr_notifications)
            ### Use RANGELIST as the mergeinfo for all children of
@@ -5190,10 +5324,11 @@ remove_absent_children(const char *targe
    drive removes explicit mergeinfo from a subtree of the merge target.
 
    MERGE_B is cascaded from the argument of the same name in
-   do_directory_merge().  If MERGE_B->DRY_RUN is true do nothing, if it is
-   false then for each path (if any) in MERGE_B->PATHS_WITH_DELETED_MERGEINFO
-   remove that path from CHILDREN_WITH_MERGEINFO by setting that
-   child to NULL.  The one exception is for the merge target itself,
+   do_directory_merge().  For each path (if any) in
+   MERGE_B->PATHS_WITH_DELETED_MERGEINFO remove that path from
+   CHILDREN_WITH_MERGEINFO.
+
+   The one exception is for the merge target itself,
    MERGE_B->target->abspath, this must always be present in
    CHILDREN_WITH_MERGEINFO so this is never removed by this
    function. */
@@ -5203,7 +5338,7 @@ remove_children_with_deleted_mergeinfo(m
 {
   int i;
 
-  if (merge_b->dry_run || !merge_b->paths_with_deleted_mergeinfo)
+  if (!merge_b->paths_with_deleted_mergeinfo)
     return;
 
   /* CHILDREN_WITH_MERGEINFO[0] is the always the merge target
@@ -5212,8 +5347,8 @@ remove_children_with_deleted_mergeinfo(m
     {
       svn_client__merge_path_t *child =
         APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
-      if (apr_hash_get(merge_b->paths_with_deleted_mergeinfo,
-                       child->abspath, APR_HASH_KEY_STRING))
+
+      if (svn_hash_gets(merge_b->paths_with_deleted_mergeinfo, child->abspath))
         {
           svn_sort__array_delete(children_with_mergeinfo, i--, 1);
         }
@@ -5492,9 +5627,6 @@ drive_merge_report_editor(const char *ta
   SVN_ERR(svn_ra_reparent(merge_b->ra_session1, old_sess1_url, scratch_pool));
   SVN_ERR(svn_ra_reparent(merge_b->ra_session2, old_sess2_url, scratch_pool));
 
-  /* Caller must call svn_sleep_for_timestamps() */
-  *(merge_b->use_sleep) = TRUE;
-
   return SVN_NO_ERROR;
 }
 
@@ -5590,13 +5722,7 @@ slice_remaining_ranges(apr_array_header_
 
    If a range is removed from a child's remaining_ranges array, allocate the
    new remaining_ranges array in POOL.
-
-   ### TODO: We should have remaining_ranges in reverse order to avoid
-   ### recreating and reallocating the remaining_ranges every time we want
-   ### to remove the first range.  If the ranges were reversed we could simply
-   ### pop the last element in the array.  Alternatively we might be able to
-   ### make svn_sort__array_delete() efficient: it could increment 'elts'
-   ### (and decrement 'nelts' and 'nalloc') instead of moving elements. */
+ */
 static void
 remove_first_range_from_remaining_ranges(svn_revnum_t revision,
                                          apr_array_header_t
@@ -5861,28 +5987,22 @@ pre_merge_status_cb(void *baton,
                     apr_pool_t *scratch_pool)
 {
   struct pre_merge_status_baton_t *pmsb = baton;
-  const char *dup_abspath = NULL;
 
   if (status->switched && !status->file_external)
     {
-      if (!dup_abspath)
-        dup_abspath = apr_pstrdup(pmsb->pool, local_abspath);
-
-      apr_hash_set(pmsb->switched_subtrees,
-                   dup_abspath, APR_HASH_KEY_STRING, dup_abspath);
+      store_path(pmsb->switched_subtrees, local_abspath);
     }
 
   if (status->depth == svn_depth_empty
       || status->depth == svn_depth_files)
     {
+      const char *dup_abspath;
       svn_depth_t *depth = apr_pmemdup(pmsb->pool, &status->depth,
                                        sizeof *depth);
 
-      if (!dup_abspath)
-        dup_abspath = apr_pstrdup(pmsb->pool, local_abspath);
+      dup_abspath = apr_pstrdup(pmsb->pool, local_abspath);
 
-      apr_hash_set(pmsb->shallow_subtrees,
-                   dup_abspath, APR_HASH_KEY_STRING, depth);
+      svn_hash_sets(pmsb->shallow_subtrees, dup_abspath, depth);
     }
 
   if (status->node_status == svn_wc_status_missing)
@@ -5890,9 +6010,6 @@ pre_merge_status_cb(void *baton,
       svn_boolean_t new_missing_root = TRUE;
       apr_hash_index_t *hi;
 
-      if (!dup_abspath)
-        dup_abspath = apr_pstrdup(pmsb->pool, local_abspath);
-
       for (hi = apr_hash_first(scratch_pool, pmsb->missing_subtrees);
            hi;
            hi = apr_hash_next(hi))
@@ -5900,7 +6017,7 @@ pre_merge_status_cb(void *baton,
           const char *missing_root_path = svn__apr_hash_index_key(hi);
 
           if (svn_dirent_is_ancestor(missing_root_path,
-                                     dup_abspath))
+                                     local_abspath))
             {
               new_missing_root = FALSE;
               break;
@@ -5908,8 +6025,7 @@ pre_merge_status_cb(void *baton,
         }
 
       if (new_missing_root)
-        apr_hash_set(pmsb->missing_subtrees,
-                     dup_abspath, APR_HASH_KEY_STRING, dup_abspath);
+        store_path(pmsb->missing_subtrees, local_abspath);
     }
 
   return SVN_NO_ERROR;
@@ -5963,10 +6079,9 @@ get_wc_explicit_mergeinfo_catalog(apr_ha
 
       /* svn_client_propget5 picks up file externals with
          mergeinfo, but we don't want those. */
-      if (apr_hash_get(externals, wc_path, APR_HASH_KEY_STRING))
+      if (svn_hash_gets(externals, wc_path))
         {
-          apr_hash_set(*subtrees_with_mergeinfo, wc_path,
-                       APR_HASH_KEY_STRING, NULL);
+          svn_hash_sets(*subtrees_with_mergeinfo, wc_path, NULL);
           continue;
         }
 
@@ -5986,8 +6101,7 @@ get_wc_explicit_mergeinfo_catalog(apr_ha
             }
           return svn_error_trace(err);
         }
-      apr_hash_set(*subtrees_with_mergeinfo, wc_path, APR_HASH_KEY_STRING,
-                   mergeinfo);
+      svn_hash_sets(*subtrees_with_mergeinfo, wc_path, mergeinfo);
     }
   svn_pool_destroy(iterpool);
 
@@ -7000,8 +7114,7 @@ filter_natural_history_from_mergeinfo(sv
       && (requested_range->start < requested_range->end))
     {
       svn_rangelist_t *implied_rangelist =
-        apr_hash_get(implicit_mergeinfo, source_rel_path,
-                     APR_HASH_KEY_STRING);
+                        svn_hash_gets(implicit_mergeinfo, source_rel_path);
 
       if (implied_rangelist)
         SVN_ERR(svn_rangelist_remove(filtered_rangelist,
@@ -7073,25 +7186,27 @@ subrange_source(const merge_source_t *so
    and the value is the new mergeinfo for that path.  Allocate additions
    to RESULT_CATALOG in pool which RESULT_CATALOG was created in.
 
+   CONFLICTED_RANGE is as documented for do_directory_merge().
+
    Note: MERGE_B->RA_SESSION1 must be associated with SOURCE->loc1->url and
    MERGE_B->RA_SESSION2 with SOURCE->loc2->url.
 */
 static svn_error_t *
 do_file_merge(svn_mergeinfo_catalog_t result_catalog,
+              svn_merge_range_t **conflicted_range,
               const merge_source_t *source,
               const char *target_abspath,
               const svn_diff_tree_processor_t *processor,
               svn_boolean_t sources_related,
               svn_boolean_t squelch_mergeinfo_notifications,
-              svn_boolean_t abort_on_conflicts,
               merge_cmd_baton_t *merge_b,
+              apr_pool_t *result_pool,
               apr_pool_t *scratch_pool)
 {
   svn_rangelist_t *remaining_ranges;
   svn_client_ctx_t *ctx = merge_b->ctx;
   svn_merge_range_t range;
   svn_mergeinfo_t target_mergeinfo;
-  svn_merge_range_t *conflicted_range = NULL;
   svn_boolean_t inherited = FALSE;
   svn_boolean_t is_rollback = (source->loc1->rev > source->loc2->rev);
   const svn_client__pathrev_t *primary_src
@@ -7102,9 +7217,9 @@ do_file_merge(svn_mergeinfo_catalog_t re
 
   SVN_ERR_ASSERT(svn_dirent_is_absolute(target_abspath));
 
-  /* Note that this is a single-file merge. */
-  merge_b->is_single_file_merge = TRUE;
+  *conflicted_range = NULL;
 
+  /* Note that this is a single-file merge. */
   range.start = source->loc1->rev;
   range.end = source->loc2->rev;

[... 1272 lines stripped ...]