You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by hw...@apache.org on 2013/01/22 00:37:04 UTC

svn commit: r1436688 [3/12] - in /subversion/branches/ev2-export: ./ contrib/client-side/emacs/ notes/commit-access-templates/ subversion/bindings/javahl/native/ subversion/bindings/swig/perl/native/t/ subversion/bindings/swig/ruby/test/ subversion/inc...

Modified: subversion/branches/ev2-export/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/ev2-export/subversion/libsvn_client/merge.c?rev=1436688&r1=1436687&r2=1436688&view=diff
==============================================================================
--- subversion/branches/ev2-export/subversion/libsvn_client/merge.c (original)
+++ subversion/branches/ev2-export/subversion/libsvn_client/merge.c Mon Jan 21 23:37:01 2013
@@ -245,7 +245,7 @@ typedef struct merge_target_t
 } merge_target_t;
 
 typedef struct merge_cmd_baton_t {
-  svn_boolean_t force;
+  svn_boolean_t force_delete;         /* Delete a file/dir even if modified */
   svn_boolean_t dry_run;
   svn_boolean_t record_only;          /* Whether to merge only mergeinfo
                                          differences. */
@@ -255,9 +255,10 @@ typedef struct merge_cmd_baton_t {
                                          is TRUE.*/
   svn_boolean_t mergeinfo_capable;    /* Whether the merge source server
                                          is capable of Merge Tracking. */
-  svn_boolean_t ignore_ancestry;      /* Are we ignoring ancestry (and by
-                                         extension, mergeinfo)?  FALSE if
-                                         SOURCES_ANCESTRAL is FALSE. */
+  svn_boolean_t ignore_ancestry;      /* Two meanings: don't honor mergeinfo;
+                                         diff unrelated nodes as if related.
+                                         See do_merge() doc string.  FALSE if
+                                         MERGE_SOURCE->ancestral is FALSE. */
   svn_boolean_t reintegrate_merge;    /* Whether this is a --reintegrate
                                          merge or not. */
   const merge_target_t *target;       /* Description of merge target node */
@@ -275,22 +276,13 @@ typedef struct merge_cmd_baton_t {
 
   svn_client_ctx_t *ctx;              /* Client context for callbacks, etc. */
 
-  /* Whether invocation of the merge_file_added() callback required
-     delegation to the merge_file_changed() function for the file
-     currently being merged.  This info is used to detect whether a
-     file on the left side of a 3-way merge actually exists (important
-     because it's created as an empty temp file on disk regardless).*/
-  svn_boolean_t add_necessitated_merge;
-
-  /* The list of paths for entries we've deleted, used only when in
+  /* The list of paths for nodes we've deleted, used only when in
      dry_run mode. */
   apr_hash_t *dry_run_deletions;
 
-  /* The list of paths for entries we've added and the most
-     recently added directory.  The latter may be NULL.
-     Both are used only when in dry_run mode. */
+  /* The list of paths for nodes we've added, used only when in
+     dry_run mode. */
   apr_hash_t *dry_run_added;
-  const char *dry_run_last_added_dir;
 
   /* The list of any paths which remained in conflict after a
      resolution attempt was made.  We track this in-memory, rather
@@ -315,11 +307,11 @@ typedef struct merge_cmd_baton_t {
 
   /* The list of absolute skipped paths, which should be examined and
      cleared after each invocation of the callback.  The paths
-     are absolute.  Is NULL if MERGE_B->SOURCES_ANCESTRAL and
+     are absolute.  Is NULL if MERGE_B->MERGE_SOURCE->ancestral and
      MERGE_B->REINTEGRATE_MERGE are both false. */
   apr_hash_t *skipped_abspaths;
 
-  /* The list of absolute merged paths.  Unused if MERGE_B->SOURCES_ANCESTRAL
+  /* The list of absolute merged paths.  Unused if MERGE_B->MERGE_SOURCE->ancestral
      and MERGE_B->REINTEGRATE_MERGE are both false. */
   apr_hash_t *merged_abspaths;
 
@@ -479,76 +471,34 @@ check_same_repos(const svn_client__pathr
   return SVN_NO_ERROR;
 }
 
-/* Return true iff we're in dry-run mode and WCPATH would have been
+/* Return true iff we're in dry-run mode and LOCAL_ABSPATH would have been
    deleted by now if we weren't in dry-run mode.
    Used to avoid spurious notifications (e.g. conflicts) from a merge
    attempt into an existing target which would have been deleted if we
-   weren't in dry_run mode (issue #2584).  Assumes that WCPATH is
-   still versioned (e.g. has an associated entry). */
+   weren't in dry_run mode (issue #2584). */
 static APR_INLINE svn_boolean_t
-dry_run_deleted_p(const merge_cmd_baton_t *merge_b, const char *wcpath)
+dry_run_deleted_p(const merge_cmd_baton_t *merge_b,
+                  const char *local_abspath)
 {
   return (merge_b->dry_run &&
-          apr_hash_get(merge_b->dry_run_deletions, wcpath,
+          apr_hash_get(merge_b->dry_run_deletions, local_abspath,
                        APR_HASH_KEY_STRING) != NULL);
 }
 
-/* Return true iff we're in dry-run mode and WCPATH would have been
+/* Return true iff we're in dry-run mode and LOCAL_ABSPATH would have been
    added by now if we weren't in dry-run mode.
    Used to avoid spurious notifications (e.g. conflicts) from a merge
    attempt into an existing target which would have been deleted if we
-   weren't in dry_run mode (issue #2584).  Assumes that WCPATH is
-   still versioned (e.g. has an associated entry). */
+   weren't in dry_run mode (issue #2584). */
 static APR_INLINE svn_boolean_t
-dry_run_added_p(const merge_cmd_baton_t *merge_b, const char *wcpath)
+dry_run_added_p(const merge_cmd_baton_t *merge_b,
+                const char *local_abspath)
 {
   return (merge_b->dry_run &&
-          apr_hash_get(merge_b->dry_run_added, wcpath,
+          apr_hash_get(merge_b->dry_run_added, local_abspath,
                        APR_HASH_KEY_STRING) != NULL);
 }
 
-/* Return true iff we're in dry-run mode and a parent of
-   LOCAL_RELPATH/LOCAL_ABSPATH would have been added by now if we
-   weren't in dry-run mode.  Used to avoid spurious notifications
-   (e.g. conflicts) from a merge attempt into an existing target which
-   would have been deleted if we weren't in dry_run mode (issue
-   #2584).  Assumes that WCPATH is still versioned (e.g. has an
-   associated entry). */
-static svn_boolean_t
-dry_run_added_parent_p(const merge_cmd_baton_t *merge_b,
-                       const char *local_relpath,
-                       const char *local_abspath,
-                       apr_pool_t *scratch_pool)
-{
-  const char *abspath = local_abspath;
-  apr_size_t i;
-
-  if (!merge_b->dry_run)
-    return FALSE;
-
-  /* See if LOCAL_ABSPATH is a child of the most recently added
-     directory.  This is an optimization over searching through
-     dry_run_added that plays to the strengths of the editor's drive
-     ordering constraints.  In fact, we need the fallback approach
-     below only because of ra_serf's insufficiencies in this area.  */
-  if (merge_b->dry_run_last_added_dir
-      && svn_dirent_is_child(merge_b->dry_run_last_added_dir,
-                             local_abspath, NULL))
-    return TRUE;
-
-  /* The fallback:  see if any of LOCAL_ABSPATH's parents have been
-     added in this merge so far. */
-  for (i = 0; i < (svn_path_component_count(local_relpath) - 1); i++)
-    {
-      abspath = svn_dirent_dirname(abspath, scratch_pool);
-      if (apr_hash_get(merge_b->dry_run_added, abspath,
-                       APR_HASH_KEY_STRING))
-        return TRUE;
-    }
-
-  return FALSE;
-}
-
 /* Return whether any WC path was put in conflict by the merge
    operation corresponding to MERGE_B. */
 static APR_INLINE svn_boolean_t
@@ -575,7 +525,6 @@ 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 *added,
                           svn_boolean_t *deleted,
                           svn_node_kind_t *kind,
                           const merge_cmd_baton_t *merge_b,
@@ -591,8 +540,6 @@ perform_obstruction_check(svn_wc_notify_
 
   *obstruction_state = svn_wc_notify_state_inapplicable;
 
-  if (added)
-    *added = FALSE;
   if (deleted)
     *deleted = FALSE;
   if (kind)
@@ -603,7 +550,8 @@ perform_obstruction_check(svn_wc_notify_
     {
       if (dry_run_deleted_p(merge_b, local_abspath))
         {
-          *obstruction_state = svn_wc_notify_state_inapplicable;
+          /* svn_wc_notify_state_inapplicable */
+          /* svn_node_none */
 
           if (deleted)
             *deleted = TRUE;
@@ -615,15 +563,22 @@ perform_obstruction_check(svn_wc_notify_
         }
       else if (dry_run_added_p(merge_b, local_abspath))
         {
-          *obstruction_state = svn_wc_notify_state_inapplicable;
+          /* svn_wc_notify_state_inapplicable */
 
-          if (added)
-            *added = TRUE;
           if (kind)
             *kind = svn_node_dir; /* Currently only used for dirs */
 
           return SVN_NO_ERROR;
         }
+      else if (dry_run_added_p(merge_b,
+                               svn_dirent_dirname(local_abspath,
+                                                  scratch_pool)))
+        {
+          /* svn_wc_notify_state_inapplicable */
+          /* svn_node_none */
+
+          return SVN_NO_ERROR;
+        }
      }
 
   if (kind == NULL)
@@ -633,7 +588,6 @@ perform_obstruction_check(svn_wc_notify_
 
   SVN_ERR(svn_wc__check_for_obstructions(obstruction_state,
                                          kind,
-                                         added,
                                          deleted,
                                          wc_ctx, local_abspath,
                                          check_root,
@@ -1393,6 +1347,56 @@ prepare_merge_props_changed(const apr_ar
   return SVN_NO_ERROR;
 }
 
+/* Indicate in *MOVED_AWAY whether the node at LOCAL_ABSPATH was
+ * moved away locally. Do not raise an error if the node at LOCAL_ABSPATH
+ * does not exist. */
+static svn_error_t *
+check_moved_away(svn_boolean_t *moved_away,
+                 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, NULL,
+                                      wc_ctx, local_abspath,
+                                      scratch_pool, scratch_pool));
+  
+  *moved_away = (moved_to_abspath != NULL);
+
+  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;
+}
+
 /* An svn_wc_diff_callbacks4_t function. */
 static svn_error_t *
 merge_dir_props_changed(svn_wc_notify_state_t *state,
@@ -1412,15 +1416,13 @@ merge_dir_props_changed(svn_wc_notify_st
   svn_boolean_t is_deleted;
   svn_node_kind_t kind;
 
-  SVN_ERR(perform_obstruction_check(&obstr_state, NULL, &is_deleted,
-                                    &kind,
+  SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &kind,
                                     merge_b, local_abspath, svn_node_dir,
                                     scratch_pool));
 
   if (obstr_state != svn_wc_notify_state_inapplicable)
     {
-      if (state)
-        *state = obstr_state;
+      *state = obstr_state;
       return SVN_NO_ERROR;
     }
 
@@ -1429,17 +1431,25 @@ merge_dir_props_changed(svn_wc_notify_st
       svn_wc_conflict_reason_t reason;
 
       if (is_deleted)
-        reason = svn_wc_conflict_reason_deleted;
+        {
+          svn_boolean_t moved_away;
+
+          SVN_ERR(check_moved_away(&moved_away, merge_b->ctx->wc_ctx,
+                                   local_abspath, scratch_pool));
+
+          if (moved_away)
+            reason = svn_wc_conflict_reason_moved_away;
+          else
+            reason = svn_wc_conflict_reason_deleted;
+        }
       else
         reason = svn_wc_conflict_reason_missing;
 
       SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_file,
                             svn_wc_conflict_action_edit, reason));
 
-      if (tree_conflicted)
-        *tree_conflicted = TRUE;
-      if (state)
-        *state = svn_wc_notify_state_missing;
+      *tree_conflicted = TRUE;
+      *state = svn_wc_notify_state_missing;
 
       return SVN_NO_ERROR;
     }
@@ -1472,7 +1482,7 @@ merge_dir_props_changed(svn_wc_notify_st
                                   ctx->cancel_func, ctx->cancel_baton,
                                   scratch_pool));
     }
-  else if (state)
+  else
     *state = svn_wc_notify_state_unchanged;
 
   return SVN_NO_ERROR;
@@ -1538,6 +1548,18 @@ conflict_resolver(svn_wc_conflict_result
   return svn_error_trace(err);
 }
 
+/* Store LOCAL_ABSPATH in PATH_HASH after duplicating it into the pool
+   containing PATH_HASH */
+static APR_INLINE void
+store_path(apr_hash_t *path_hash, const char *local_abspath)
+{
+  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);
+}
+
+
 /* An svn_wc_diff_callbacks4_t function. */
 static svn_error_t *
 merge_file_opened(svn_boolean_t *tree_conflicted,
@@ -1550,67 +1572,6 @@ merge_file_opened(svn_boolean_t *tree_co
   return SVN_NO_ERROR;
 }
 
-
-/* Indicate in *MOVED_AWAY whether the node at LOCAL_ABSPATH was
- * moved away locally. Do not raise an error if the node at LOCAL_ABSPATH
- * does not exist. */
-static svn_error_t *
-check_moved_away(svn_boolean_t *moved_away,
-                 svn_wc_context_t *wc_ctx,
-                 const char *local_abspath,
-                 apr_pool_t *scratch_pool)
-{
-  const char *moved_to_abspath;
-  svn_error_t *err;
-
-  *moved_away = FALSE;
-
-  err = svn_wc__node_was_moved_away(&moved_to_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_to_abspath)
-    *moved_away = TRUE;
-
-  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;
-}
-
 /* An svn_wc_diff_callbacks4_t function. */
 static svn_error_t *
 merge_file_changed(svn_wc_notify_state_t *content_state,
@@ -1641,15 +1602,11 @@ merge_file_changed(svn_wc_notify_state_t
   SVN_ERR_ASSERT(!older_abspath || svn_dirent_is_absolute(older_abspath));
   SVN_ERR_ASSERT(!yours_abspath || svn_dirent_is_absolute(yours_abspath));
 
-  if (tree_conflicted)
-    *tree_conflicted = FALSE;
-
   /* Check for an obstructed or missing node on disk. */
   {
     svn_wc_notify_state_t obstr_state;
 
-    SVN_ERR(perform_obstruction_check(&obstr_state, NULL,
-                                      &is_deleted, &wc_kind,
+    SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &wc_kind,
                                       merge_b, local_abspath, svn_node_unknown,
                                       scratch_pool));
     if (obstr_state != svn_wc_notify_state_inapplicable)
@@ -1658,9 +1615,8 @@ merge_file_changed(svn_wc_notify_state_t
          * but the working file is missing, maybe we can 'restore' the
          * working file from the text-base, and then allow the merge to run? */
 
-        if (content_state)
-          *content_state = obstr_state;
-        if (prop_state && obstr_state == svn_wc_notify_state_missing)
+        *content_state = obstr_state;
+        if (obstr_state == svn_wc_notify_state_missing)
           *prop_state = svn_wc_notify_state_missing;
         return SVN_NO_ERROR;
       }
@@ -1691,10 +1647,8 @@ merge_file_changed(svn_wc_notify_state_t
           if (parent_depth < svn_depth_files
               && parent_depth != svn_depth_unknown)
             {
-              if (content_state)
-                *content_state = svn_wc_notify_state_missing;
-              if (prop_state)
-                *prop_state = svn_wc_notify_state_missing;
+              *content_state = svn_wc_notify_state_missing;
+              *prop_state = svn_wc_notify_state_missing;
               return SVN_NO_ERROR;
             }
         }
@@ -1702,22 +1656,22 @@ merge_file_changed(svn_wc_notify_state_t
       /* This is use case 4 described in the paper attached to issue
        * #2282.  See also notes/tree-conflicts/detection.txt
        */
-      SVN_ERR(check_moved_away(&moved_away, ctx->wc_ctx,
-                               local_abspath, scratch_pool));
-      if (moved_away)
-        reason = svn_wc_conflict_reason_moved_away;
-      else if (is_deleted)
-        reason = svn_wc_conflict_reason_deleted;
+      if (is_deleted)
+        {
+          SVN_ERR(check_moved_away(&moved_away, ctx->wc_ctx,
+                                   local_abspath, scratch_pool));
+          if (moved_away)
+            reason = svn_wc_conflict_reason_moved_away;
+          else
+            reason = svn_wc_conflict_reason_deleted;
+        }
       else
         reason = svn_wc_conflict_reason_missing;
       SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_file,
                             svn_wc_conflict_action_edit, reason));
-      if (tree_conflicted)
-        *tree_conflicted = TRUE;
-      if (content_state)
-        *content_state = svn_wc_notify_state_missing;
-      if (prop_state)
-        *prop_state = svn_wc_notify_state_missing;
+      *tree_conflicted = TRUE;
+      *content_state = svn_wc_notify_state_missing;
+      *prop_state = svn_wc_notify_state_missing;
       return SVN_NO_ERROR;
     }
 
@@ -1739,8 +1693,7 @@ merge_file_changed(svn_wc_notify_state_t
      diff-editor-mechanisms are doing the hard work of getting the
      fulltexts! */
 
-  if (prop_state)
-    *prop_state = svn_wc_notify_state_unchanged;
+  *prop_state = svn_wc_notify_state_unchanged;
 
   SVN_ERR(prepare_merge_props_changed(&prop_changes, local_abspath,
                                       prop_changes, merge_b,
@@ -1764,8 +1717,7 @@ merge_file_changed(svn_wc_notify_state_t
   /* Easy out: We are only applying mergeinfo differences. */
   if (merge_b->record_only)
     {
-      if (content_state)
-        *content_state = svn_wc_notify_state_unchanged;
+      *content_state = svn_wc_notify_state_unchanged;
       return SVN_NO_ERROR;
     }
 
@@ -1810,20 +1762,17 @@ merge_file_changed(svn_wc_notify_state_t
                             ctx->cancel_baton,
                             scratch_pool));
 
-      if (content_state)
-        {
-          if (content_outcome == svn_wc_merge_conflict)
-            *content_state = svn_wc_notify_state_conflicted;
-          else if (has_local_mods
-                   && content_outcome != svn_wc_merge_unchanged)
-            *content_state = svn_wc_notify_state_merged;
-          else if (content_outcome == svn_wc_merge_merged)
-            *content_state = svn_wc_notify_state_changed;
-          else if (content_outcome == svn_wc_merge_no_merge)
-            *content_state = svn_wc_notify_state_missing;
-          else /* merge_outcome == svn_wc_merge_unchanged */
-            *content_state = svn_wc_notify_state_unchanged;
-        }
+      if (content_outcome == svn_wc_merge_conflict)
+        *content_state = svn_wc_notify_state_conflicted;
+      else if (has_local_mods
+               && content_outcome != svn_wc_merge_unchanged)
+        *content_state = svn_wc_notify_state_merged;
+      else if (content_outcome == svn_wc_merge_merged)
+        *content_state = svn_wc_notify_state_changed;
+      else if (content_outcome == svn_wc_merge_no_merge)
+        *content_state = svn_wc_notify_state_missing;
+      else /* merge_outcome == svn_wc_merge_unchanged */
+        *content_state = svn_wc_notify_state_unchanged;
     }
 
   return SVN_NO_ERROR;
@@ -1849,33 +1798,27 @@ merge_file_added(svn_wc_notify_state_t *
                  apr_pool_t *scratch_pool)
 {
   merge_cmd_baton_t *merge_b = baton;
-  const char *mine_abspath = svn_dirent_join(merge_b->target->abspath,
-                                             mine_relpath, scratch_pool);
-  svn_node_kind_t wc_kind;
+  const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
+                                              mine_relpath, scratch_pool);
   svn_node_kind_t kind;
+  svn_boolean_t is_deleted;
   int i;
   apr_hash_t *file_props;
 
-  SVN_ERR_ASSERT(svn_dirent_is_absolute(mine_abspath));
-
-  if (tree_conflicted)
-    *tree_conflicted = FALSE;
+  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
 
   /* Easy out: We are only applying mergeinfo differences. */
   if (merge_b->record_only)
     {
-      if (content_state)
-        *content_state = svn_wc_notify_state_unchanged;
-      if (prop_state)
-        *prop_state = svn_wc_notify_state_unchanged;
+      *content_state = svn_wc_notify_state_unchanged;
+      *prop_state = svn_wc_notify_state_unchanged;
       return SVN_NO_ERROR;
     }
 
   /* In most cases, we just leave prop_state as unknown, and let the
      content_state what happened, so we set prop_state here to avoid that
      below. */
-  if (prop_state)
-    *prop_state = svn_wc_notify_state_unknown;
+  *prop_state = svn_wc_notify_state_unknown;
 
   /* Apply the prop changes to a new hash table. */
   file_props = apr_hash_copy(scratch_pool, original_props);
@@ -1909,207 +1852,160 @@ merge_file_added(svn_wc_notify_state_t *
   {
     svn_wc_notify_state_t obstr_state;
 
-    SVN_ERR(perform_obstruction_check(&obstr_state, NULL, NULL,
-                                      &wc_kind,
-                                      merge_b, mine_abspath, svn_node_unknown,
+    SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &kind,
+                                      merge_b, local_abspath, svn_node_unknown,
                                       scratch_pool));
 
     if (obstr_state != svn_wc_notify_state_inapplicable)
       {
-        if (merge_b->dry_run 
-            && dry_run_added_parent_p(merge_b, mine_relpath,
-                                      mine_abspath, scratch_pool))
-          {
-            if (content_state)
-              *content_state = svn_wc_notify_state_changed;
-            if (prop_state && apr_hash_count(file_props))
-              *prop_state = svn_wc_notify_state_changed;
-          }
-        else if (content_state)
-          *content_state = obstr_state;
+        *content_state = obstr_state;
 
         return SVN_NO_ERROR;
       }
+
+    if (is_deleted)
+      kind = svn_node_none;
   }
 
-  SVN_ERR(svn_io_check_path(mine_abspath, &kind, scratch_pool));
-  switch (kind)
+  if (kind != svn_node_none)
     {
-    case svn_node_none:
-      {
-        svn_node_kind_t parent_kind;
-
-        /* Does the parent exist on disk (vs missing). If no we should
-           report an obstruction. Or svn_wc_add_repos_file4() will just
-           do its work and the workqueue will create the missing dirs */
-        SVN_ERR(svn_io_check_path(
-                        svn_dirent_dirname(mine_abspath, scratch_pool), 
-                        &parent_kind, scratch_pool));
-
-        if (parent_kind != svn_node_dir)
-          {
-            *content_state = svn_wc_notify_state_obstructed;
-            return SVN_NO_ERROR;
-          }
-
-        if (! merge_b->dry_run)
-          {
-            const char *copyfrom_url;
-            svn_revnum_t copyfrom_rev;
-            svn_stream_t *new_contents, *new_base_contents;
-            apr_hash_t *new_base_props, *new_props;
-            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.
-               Otherwise, we'll use a pure add. */
-            if (merge_b->same_repos)
-              {
-                const char *child =
-                  svn_dirent_skip_ancestor(merge_b->target->abspath,
-                                           mine_abspath);
-                SVN_ERR_ASSERT(child != NULL);
-                copyfrom_url = svn_path_url_add_component2(
-                                             merge_b->merge_source.loc2->url,
-                                             child, scratch_pool);
-                copyfrom_rev = rev2;
-                SVN_ERR(check_repos_match(merge_b->target, mine_abspath,
-                                          copyfrom_url, scratch_pool));
-                new_base_props = file_props;
-                new_props = NULL; /* inherit from new_base_props */
-                SVN_ERR(svn_stream_open_readonly(&new_base_contents,
-                                                 yours_abspath,
-                                                 scratch_pool,
-                                                 scratch_pool));
-                new_contents = NULL; /* inherit from new_base_contents */
-              }
-            else
-              {
-                copyfrom_url = NULL;
-                copyfrom_rev = SVN_INVALID_REVNUM;
-                new_base_props = apr_hash_make(scratch_pool);
-                new_props = file_props;
-                new_base_contents = svn_stream_empty(scratch_pool);
-                SVN_ERR(svn_stream_open_readonly(&new_contents, yours_abspath,
-                                                 scratch_pool, scratch_pool));
-              }
-
-            err = svn_wc_conflicted_p3(NULL, NULL, &existing_tree_conflict,
-                                       merge_b->ctx->wc_ctx, mine_abspath,
-                                       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_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,
-                                         mine_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, mine_abspath,
-                                             svn_node_file,
-                                             svn_wc_conflict_action_add,
-                                             reason));
-                if (tree_conflicted)
-                  *tree_conflicted = TRUE;
-              }
-            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,
-                                               mine_abspath,
-                                               new_base_contents,
-                                               new_contents,
-                                               new_base_props, new_props,
-                                               copyfrom_url, copyfrom_rev,
-                                               merge_b->ctx->cancel_func,
-                                               merge_b->ctx->cancel_baton,
-                                               scratch_pool));
-              }
-          }
-        if (content_state)
-          *content_state = svn_wc_notify_state_changed;
-        if (prop_state && apr_hash_count(file_props))
-          *prop_state = svn_wc_notify_state_changed;
-      }
-      break;
-    case svn_node_dir:
-      /* The file add the merge wants to carry out is obstructed by
-       * a directory, so the file the merge wants to add is a tree
-       * conflict victim.
+      /* The file add the merge wants to carry out is obstructed, so the
+       * file the merge wants to add is a tree conflict victim.
        * See notes about obstructions in notes/tree-conflicts/detection.txt.
        */
-      SVN_ERR(tree_conflict_on_add(merge_b, mine_abspath, svn_node_file,
+      SVN_ERR(tree_conflict_on_add(merge_b, local_abspath, svn_node_file,
                                    svn_wc_conflict_action_add,
                                    svn_wc_conflict_reason_obstructed));
-      if (tree_conflicted)
-        *tree_conflicted = TRUE;
-      if (content_state)
-        {
-          /* directory already exists, is it under version control? */
-          if ((wc_kind != svn_node_none)
-              && dry_run_deleted_p(merge_b, mine_abspath))
-            *content_state = svn_wc_notify_state_changed;
-          else
-            /* this will make the repos_editor send a 'skipped' message */
-            *content_state = svn_wc_notify_state_obstructed;
+      *tree_conflicted = TRUE;
+      
+      /* directory already exists, is it under version control? */
+      if ((kind != svn_node_none)
+          && dry_run_deleted_p(merge_b, local_abspath))
+        *content_state = svn_wc_notify_state_changed;
+      else
+        /* this will make the repos_editor send a 'skipped' message */
+        *content_state = svn_wc_notify_state_obstructed;
+      return SVN_NO_ERROR;
+    }
+
+  if (!merge_b->dry_run)
+    {
+      const char *copyfrom_url;
+      svn_revnum_t copyfrom_rev;
+      svn_stream_t *new_contents, *new_base_contents;
+      apr_hash_t *new_base_props, *new_props;
+      svn_boolean_t existing_tree_conflict;
+      svn_error_t *err;
+      svn_node_kind_t parent_kind;
+
+
+      /* Does the parent exist on disk (vs missing). If no we should
+         report an obstruction. Or svn_wc_add_repos_file4() will just
+         do its work and the workqueue will create the missing dirs */
+      SVN_ERR(svn_io_check_path(
+                  svn_dirent_dirname(local_abspath, scratch_pool), 
+                  &parent_kind, scratch_pool));
+
+      if (parent_kind != svn_node_dir)
+        {
+          *content_state = svn_wc_notify_state_obstructed;
+          return SVN_NO_ERROR;
         }
-      break;
-    case svn_node_file:
-      {
-            if (dry_run_deleted_p(merge_b, mine_abspath))
-              {
-                if (content_state)
-                  *content_state = svn_wc_notify_state_changed;
-              }
-            else
-              {
-                svn_boolean_t moved_here;
-                svn_wc_conflict_reason_t reason;
-
-                /* The file add the merge wants to carry out is obstructed by
-                 * a versioned file. This file must have been added in the
-                 * history of the merge target, hence we flag a tree conflict
-                 * with reason 'added'. */
-                SVN_ERR(check_moved_here(&moved_here, merge_b->ctx->wc_ctx,
-                                         mine_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, mine_abspath, svn_node_file,
-                          svn_wc_conflict_action_add, reason));
-
-                if (tree_conflicted)
-                  *tree_conflicted = TRUE;
-              }
-        break;
-      }
-    default:
-      if (content_state)
-        *content_state = svn_wc_notify_state_unknown;
-      break;
+
+      /* If this is a merge from the same repository as our
+         working copy, we handle adds as add-with-history.
+         Otherwise, we'll use a pure add. */
+      if (merge_b->same_repos)
+        {
+          const char *child =
+            svn_dirent_skip_ancestor(merge_b->target->abspath,
+                                     local_abspath);
+          SVN_ERR_ASSERT(child != NULL);
+          copyfrom_url = svn_path_url_add_component2(
+                                       merge_b->merge_source.loc2->url,
+                                       child, scratch_pool);
+          copyfrom_rev = rev2;
+          SVN_ERR(check_repos_match(merge_b->target, local_abspath,
+                                    copyfrom_url, scratch_pool));
+          new_base_props = file_props;
+          new_props = NULL; /* inherit from new_base_props */
+          SVN_ERR(svn_stream_open_readonly(&new_base_contents,
+                                           yours_abspath,
+                                           scratch_pool,
+                                           scratch_pool));
+          new_contents = NULL; /* inherit from new_base_contents */
+        }
+      else
+        {
+          copyfrom_url = NULL;
+          copyfrom_rev = SVN_INVALID_REVNUM;
+          new_base_props = apr_hash_make(scratch_pool);
+          new_props = file_props;
+          new_base_contents = svn_stream_empty(scratch_pool);
+          SVN_ERR(svn_stream_open_readonly(&new_contents, yours_abspath,
+                                           scratch_pool, scratch_pool));
+        }
+
+      err = svn_wc_conflicted_p3(NULL, NULL, &existing_tree_conflict,
+                                 merge_b->ctx->wc_ctx, local_abspath,
+                                 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_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));
+          *tree_conflicted = TRUE;
+        }
+      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,
+                                         new_base_contents,
+                                         new_contents,
+                                         new_base_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); */
     }
 
+  *content_state = svn_wc_notify_state_changed;
+  *prop_state = svn_wc_notify_state_changed;
+
   return SVN_NO_ERROR;
 }
 
@@ -2205,25 +2101,16 @@ merge_file_deleted(svn_wc_notify_state_t
                    apr_pool_t *scratch_pool)
 {
   merge_cmd_baton_t *merge_b = baton;
-  const char *mine_abspath = svn_dirent_join(merge_b->target->abspath,
-                                             mine_relpath, scratch_pool);
+  const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
+                                              mine_relpath, scratch_pool);
   svn_node_kind_t kind;
-
-  if (tree_conflicted)
-    *tree_conflicted = FALSE;
-
-  if (merge_b->dry_run)
-    {
-      const char *wcpath = apr_pstrdup(merge_b->pool, mine_abspath);
-      apr_hash_set(merge_b->dry_run_deletions, wcpath,
-                   APR_HASH_KEY_STRING, wcpath);
-    }
+  svn_boolean_t is_deleted;
+  svn_boolean_t same;
 
   /* Easy out: We are only applying mergeinfo differences. */
   if (merge_b->record_only)
     {
-      if (state)
-        *state = svn_wc_notify_state_unchanged;
+      *state = svn_wc_notify_state_unchanged;
       return SVN_NO_ERROR;
     }
 
@@ -2231,128 +2118,94 @@ merge_file_deleted(svn_wc_notify_state_t
   {
     svn_wc_notify_state_t obstr_state;
 
-    SVN_ERR(perform_obstruction_check(&obstr_state, NULL, NULL,
-                                      NULL,
-                                      merge_b, mine_abspath, svn_node_unknown,
+    SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &kind,
+                                      merge_b, local_abspath, svn_node_unknown,
                                       scratch_pool));
 
     if (obstr_state != svn_wc_notify_state_inapplicable)
       {
-        if (state)
-          *state = obstr_state;
+        *state = obstr_state;
         return SVN_NO_ERROR;
       }
   }
 
-  SVN_ERR(svn_io_check_path(mine_abspath, &kind, scratch_pool));
-  switch (kind)
+  if (merge_b->dry_run)
     {
-    case svn_node_file:
-      {
-        svn_boolean_t same;
+      const char *wcpath = apr_pstrdup(merge_b->pool, local_abspath);
+      /* Store deletion *after* obstruction check, or the registration will be
+         noted as an obstruction */
+      apr_hash_set(merge_b->dry_run_deletions, wcpath,
+                   APR_HASH_KEY_STRING, wcpath);
+    }
 
-        /* If the files are identical, attempt deletion */
-        SVN_ERR(files_same_p(&same, older_abspath, original_props,
-                             mine_abspath, merge_b->ctx->wc_ctx,
-                             scratch_pool));
-        if (same || merge_b->force || 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(mine_abspath, TRUE,
-                                          merge_b->dry_run, FALSE, NULL, NULL,
-                                          merge_b->ctx, scratch_pool));
-            if (state)
-              *state = svn_wc_notify_state_changed;
-
-            /* Record that we might have deleted mergeinfo */
-            if (!merge_b->paths_with_deleted_mergeinfo)
-              merge_b->paths_with_deleted_mergeinfo =
-                                                apr_hash_make(merge_b->pool);
-
-            apr_hash_set(merge_b->paths_with_deleted_mergeinfo,
-                         apr_pstrdup(merge_b->pool, mine_abspath),
-                         APR_HASH_KEY_STRING, mine_abspath);
-          }
-        else
-          {
-            /* The files differ, so raise a conflict instead of deleting */
+  if (kind != svn_node_file || is_deleted)
+    {
+      svn_wc_conflict_reason_t reason;
 
-            /* This might be use case 5 described in the paper attached to issue
-             * #2282.  See also notes/tree-conflicts/detection.txt
-             */
-            SVN_ERR(tree_conflict(merge_b, mine_abspath, svn_node_file,
-                                  svn_wc_conflict_action_delete,
-                                  svn_wc_conflict_reason_edited));
-            if (tree_conflicted)
-              *tree_conflicted = TRUE;
+      if (is_deleted)
+        {
+          svn_boolean_t moved_away;
 
-            if (state)
-              *state = svn_wc_notify_state_obstructed;
-          }
-      }
-      break;
-    case svn_node_dir:
-      /* The file deletion the merge wants to carry out is obstructed by
-       * a directory, so the file the merge wants to delete is a tree
-       * conflict victim.
-       * See notes about obstructions in notes/tree-conflicts/detection.txt.
-       */
-      SVN_ERR(tree_conflict(merge_b, mine_abspath, svn_node_file,
-                            svn_wc_conflict_action_delete,
-                            svn_wc_conflict_reason_obstructed));
-      if (tree_conflicted)
-        *tree_conflicted = TRUE;
-      if (state)
-        *state = svn_wc_notify_state_obstructed;
-      break;
-    case svn_node_none:
-      {
-        svn_boolean_t moved_away;
-        svn_wc_conflict_reason_t reason;
+          SVN_ERR(check_moved_away(&moved_away, merge_b->ctx->wc_ctx,
+                                   local_abspath, scratch_pool));
 
-        /* The file deleted in the diff does not exist at the current URL.
-         *
-         * This is use case 6 described in the paper attached to issue
-         * #2282.  See also notes/tree-conflicts/detection.txt
-         */
-        SVN_ERR(check_moved_away(&moved_away, merge_b->ctx->wc_ctx,
-                                 mine_abspath, scratch_pool));
-        reason = moved_away ? svn_wc_conflict_reason_moved_away
-                            : svn_wc_conflict_reason_deleted;
-        SVN_ERR(tree_conflict(merge_b, mine_abspath, svn_node_file,
-                              svn_wc_conflict_action_delete, reason));
-        if (tree_conflicted)
-          *tree_conflicted = TRUE;
-        if (state)
-          *state = svn_wc_notify_state_missing;
-      }
-      break;
-    default:
-      if (state)
-        *state = svn_wc_notify_state_unknown;
-      break;
-    }
+          if (moved_away)
+            reason = svn_wc_conflict_reason_moved_away;
+          else
+            reason = svn_wc_conflict_reason_deleted;
+        }
+      else
+        reason = svn_wc_conflict_reason_missing;
 
-  return SVN_NO_ERROR;
-}
+      SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_file,
+                            svn_wc_conflict_action_delete, reason));
 
-/* Upate dry run list of added directories.
+      *tree_conflicted = TRUE;
+      *state = svn_wc_notify_state_missing;
 
-   If the merge is a dry run, then set MERGE_B->DRY_RUN_LAST_ADDED_DIR to a
-   copy of ADDED_DIR_ABSPATH, allocated in MERGE_B->POOL. Add the same copy
-   to MERGE_B->DRY_RUN_ADDED. Do nothing if the merge is not a dry run. */
-static void
-cache_last_added_dir(merge_cmd_baton_t *merge_b,
-                     const char *added_dir_abspath)
-{
-  if (merge_b->dry_run)
+      return SVN_NO_ERROR;
+    }
+
+  
+
+  /* If the files are identical, attempt deletion */
+  SVN_ERR(files_same_p(&same, older_abspath, original_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));
+      *state = svn_wc_notify_state_changed;
+
+      /* Record that we might have deleted mergeinfo */
+      if (!merge_b->paths_with_deleted_mergeinfo)
+        merge_b->paths_with_deleted_mergeinfo =
+                                          apr_hash_make(merge_b->pool);
+
+      apr_hash_set(merge_b->paths_with_deleted_mergeinfo,
+                   apr_pstrdup(merge_b->pool, local_abspath),
+                   APR_HASH_KEY_STRING, local_abspath);
+    }
+  else
     {
-      merge_b->dry_run_last_added_dir = apr_pstrdup(merge_b->pool,
-                                                   added_dir_abspath);
-      apr_hash_set(merge_b->dry_run_added, merge_b->dry_run_last_added_dir,
-                  APR_HASH_KEY_STRING, merge_b->dry_run_last_added_dir);
+      /* The files differ, so raise a conflict instead of deleting */
+
+      /* This might be 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));
+      *tree_conflicted = TRUE;
+
+      *state = svn_wc_notify_state_obstructed;
     }
+  
+  return SVN_NO_ERROR;
 }
 
 /* An svn_wc_diff_callbacks4_t function. */
@@ -2372,30 +2225,31 @@ merge_dir_added(svn_wc_notify_state_t *s
   const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
                                               local_relpath, scratch_pool);
   svn_node_kind_t kind;
-  const char *copyfrom_url = NULL, *child;
+  const char *copyfrom_url = NULL;
   svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
-  const char *parent_abspath;
-  svn_boolean_t is_versioned;
   svn_boolean_t is_deleted;
+  svn_boolean_t add_existing = FALSE;
 
   /* Easy out: We are only applying mergeinfo differences. */
   if (merge_b->record_only)
     {
-      if (state)
-        *state = svn_wc_notify_state_unchanged;
+      *state = svn_wc_notify_state_unchanged;
       return SVN_NO_ERROR;
     }
 
-  parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
-
-  child = svn_dirent_is_child(merge_b->target->abspath, local_abspath, NULL);
-  SVN_ERR_ASSERT(child != NULL);
 
   /* If this is a merge from the same repository as our working copy,
      we handle adds as add-with-history.  Otherwise, we'll use a pure
      add. */
   if (merge_b->same_repos)
     {
+      const char *parent_abspath;
+      const char *child;
+
+      parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+      child = svn_dirent_is_child(merge_b->target->abspath, local_abspath, NULL);
+      SVN_ERR_ASSERT(child != NULL);
+
       copyfrom_url = svn_path_url_add_component2(merge_b->merge_source.loc2->url,
                                                  child, scratch_pool);
       copyfrom_rev = rev;
@@ -2408,13 +2262,10 @@ merge_dir_added(svn_wc_notify_state_t *s
   {
     svn_wc_notify_state_t obstr_state;
 
-    SVN_ERR(perform_obstruction_check(&obstr_state, NULL,
-                                      &is_deleted, &kind,
+    SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &kind,
                                       merge_b, local_abspath, svn_node_unknown,
                                       scratch_pool));
 
-    is_versioned = (kind == svn_node_dir) || (kind == svn_node_file);
-
     /* In this case of adding a directory, we have an exception to the usual
      * "skip if it's inconsistent" rule. If the directory exists on disk
      * unexpectedly, we simply make it versioned, because we can do so without
@@ -2431,22 +2282,13 @@ merge_dir_added(svn_wc_notify_state_t *s
         if (disk_kind == svn_node_dir)
           {
             obstr_state = svn_wc_notify_state_inapplicable;
-            kind = svn_node_dir; /* Take over existing directory */
+            add_existing = TRUE; /* Take over existing directory */
           }
       }
 
     if (obstr_state != svn_wc_notify_state_inapplicable)
       {
-        if (state && merge_b->dry_run
-            && dry_run_added_parent_p(merge_b, local_relpath,
-                                      local_abspath, scratch_pool))
-          {
-            *state = svn_wc_notify_state_changed;
-          }
-        else if (state)
-          {
-            *state = obstr_state;
-          }
+        *state = obstr_state;
         return SVN_NO_ERROR;
       }
 
@@ -2454,118 +2296,52 @@ merge_dir_added(svn_wc_notify_state_t *s
       kind = svn_node_none;
   }
 
-  /* Switch on the on-disk state of this path */
-  switch (kind)
+  if (kind != svn_node_none && !add_existing)
     {
-    case svn_node_none:
-      /* Unversioned or schedule-delete */
-      if (merge_b->dry_run)
-        {
-          cache_last_added_dir(merge_b, local_abspath);
-        }
+      /* The directory add the merge wants to carry out is obstructed, so the
+       * directory the merge wants to add is a tree conflict victim.
+       * See notes about obstructions in notes/tree-conflicts/detection.txt.
+       */
+      SVN_ERR(tree_conflict_on_add(merge_b, local_abspath, svn_node_dir,
+                                   svn_wc_conflict_action_add,
+                                   svn_wc_conflict_reason_obstructed));
+      *tree_conflicted = TRUE;
+      *skip = TRUE;
+      *skip_children = TRUE;
+
+      /* directory already exists, is it under version control? */
+      if ((kind != svn_node_none)
+          && dry_run_deleted_p(merge_b, local_abspath))
+        *state = svn_wc_notify_state_changed;
       else
+        /* this will make the repos_editor send a 'skipped' message */
+        *state = svn_wc_notify_state_obstructed;
+
+      return SVN_NO_ERROR;
+    }
+
+
+  /* Unversioned or schedule-delete */
+  if (merge_b->dry_run)
+    store_path(merge_b->dry_run_added, local_abspath);
+  else
+    {
+      if (!add_existing)
         {
           SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT,
                                   scratch_pool));
-          SVN_ERR(svn_wc_add4(merge_b->ctx->wc_ctx, local_abspath,
-                              svn_depth_infinity,
-                              copyfrom_url, copyfrom_rev,
-                              merge_b->ctx->cancel_func,
-                              merge_b->ctx->cancel_baton,
-                              NULL, NULL, /* don't pass notification func! */
-                              scratch_pool));
-
         }
-      if (state)
-        *state = svn_wc_notify_state_changed;
-      break;
-    case svn_node_dir:
-      /* Adding an unversioned directory doesn't destroy data */
-      if (! is_versioned || is_deleted)
-        {
-          /* The dir is not known to Subversion, or is schedule-delete.
-           * We will make it schedule-add. */
-          if (!merge_b->dry_run)
-            {
-              SVN_ERR(svn_wc_add4(merge_b->ctx->wc_ctx, local_abspath,
-                                  svn_depth_infinity,
-                                  copyfrom_url, copyfrom_rev,
-                                  merge_b->ctx->cancel_func,
-                                  merge_b->ctx->cancel_baton,
-                                  NULL, NULL, /* no notification func! */
-                                  scratch_pool));
-            }
-          else
-            {
-              cache_last_added_dir(merge_b, local_abspath);
-            }
-          if (state)
-            *state = svn_wc_notify_state_changed;
-        }
-      else
-        {
-          /* The dir is known to Subversion as already existing. */
-          if (dry_run_deleted_p(merge_b, local_abspath))
-            {
-              if (state)
-                *state = svn_wc_notify_state_changed;
-            }
-          else
-            {
-              svn_boolean_t moved_here;
-              svn_wc_conflict_reason_t reason;
 
-              /* This is a tree conflict. */
-              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_dir,
-                                           svn_wc_conflict_action_add,
-                                           reason));
-              if (tree_conflicted)
-                *tree_conflicted = TRUE;
-              if (skip)
-                *skip = TRUE;
-              if (skip_children)
-                *skip_children = TRUE;
-              if (state)
-                *state = svn_wc_notify_state_obstructed;
-            }
-        }
-      break;
-    case svn_node_file:
-      if (merge_b->dry_run)
-        merge_b->dry_run_last_added_dir = NULL;
-
-      if (is_versioned && dry_run_deleted_p(merge_b, local_abspath))
-        {
-          /* ### TODO: Retain record of this dir being added to
-             ### avoid problems from subsequent edits which try to
-             ### add children. */
-          if (state)
-            *state = svn_wc_notify_state_changed;
-        }
-      else
-        {
-          /* Obstructed: we can't add a dir because there's a file here. */
-          SVN_ERR(tree_conflict_on_add(merge_b, local_abspath, svn_node_dir,
-                                       svn_wc_conflict_action_add,
-                                       svn_wc_conflict_reason_obstructed));
-          if (tree_conflicted)
-            *tree_conflicted = TRUE;
-          if (state)
-            *state = svn_wc_notify_state_obstructed;
-        }
-      break;
-    default:
-      if (merge_b->dry_run)
-        merge_b->dry_run_last_added_dir = NULL;
-      if (state)
-        *state = svn_wc_notify_state_unknown;
-      break;
+      SVN_ERR(svn_wc_add4(merge_b->ctx->wc_ctx, local_abspath,
+                          svn_depth_infinity,
+                          copyfrom_url, copyfrom_rev,
+                          merge_b->ctx->cancel_func,
+                          merge_b->ctx->cancel_baton,
+                          NULL, NULL, /* don't pass notification func! */
+                          scratch_pool));
+
     }
+  *state = svn_wc_notify_state_changed;
 
   return SVN_NO_ERROR;
 }
@@ -2582,14 +2358,13 @@ merge_dir_deleted(svn_wc_notify_state_t 
   const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
                                               local_relpath, scratch_pool);
   svn_node_kind_t kind;
-  svn_boolean_t is_versioned;
   svn_boolean_t is_deleted;
+  svn_error_t *err;
 
   /* Easy out: We are only applying mergeinfo differences. */
   if (merge_b->record_only)
     {
-      if (state)
-        *state = svn_wc_notify_state_unchanged;
+      *state = svn_wc_notify_state_unchanged;
       return SVN_NO_ERROR;
     }
 
@@ -2597,17 +2372,13 @@ merge_dir_deleted(svn_wc_notify_state_t 
   {
     svn_wc_notify_state_t obstr_state;
 
-    SVN_ERR(perform_obstruction_check(&obstr_state, NULL,
-                                      &is_deleted, &kind,
+    SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &kind,
                                       merge_b, local_abspath, svn_node_unknown,
                                       scratch_pool));
 
-    is_versioned = (kind == svn_node_dir) || (kind == svn_node_file);
-
     if (obstr_state != svn_wc_notify_state_inapplicable)
       {
-        if (state)
-          *state = obstr_state;
+        *state = obstr_state;
         return SVN_NO_ERROR;
       }
 
@@ -2616,112 +2387,73 @@ merge_dir_deleted(svn_wc_notify_state_t 
   }
 
   if (merge_b->dry_run)
+    store_path(merge_b->dry_run_deletions, local_abspath);
+
+  if (kind != svn_node_dir || is_deleted)
     {
-      const char *wcpath = apr_pstrdup(merge_b->pool, local_abspath);
-      apr_hash_set(merge_b->dry_run_deletions, wcpath,
-                   APR_HASH_KEY_STRING, wcpath);
+      svn_wc_conflict_reason_t reason;
+
+      if (is_deleted)
+        {
+          svn_boolean_t moved_away;
+
+          SVN_ERR(check_moved_away(&moved_away, merge_b->ctx->wc_ctx,
+                                   local_abspath, scratch_pool));
+
+          if (moved_away)
+            reason = svn_wc_conflict_reason_moved_away;
+          else
+            reason = svn_wc_conflict_reason_deleted;
+        }
+      else
+        reason = svn_wc_conflict_reason_missing;
+
+      SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_dir,
+                            svn_wc_conflict_action_delete, reason));
+
+      *tree_conflicted = TRUE;
+      *state = svn_wc_notify_state_missing;
+
+      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.
+   */
 
-  /* Switch on the wc state of this path */
-  switch (kind)
+  /* 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)
     {
-    case svn_node_dir:
-      {
-        if (is_versioned && !is_deleted)
-          {
-            svn_error_t *err;
+      svn_error_clear(err);
 
-            /* ### 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.
-             */
-
-            /* 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,
-                                        merge_b->dry_run, FALSE,
-                                        NULL, NULL,
-                                        merge_b->ctx, scratch_pool);
-            if (err)
-              {
-                svn_error_clear(err);
-
-                /* 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));
-                if (tree_conflicted)
-                  *tree_conflicted = TRUE;
-                if (state)
-                  *state = svn_wc_notify_state_conflicted;
-              }
-            else
-              {
-                if (state)
-                  *state = svn_wc_notify_state_changed;
-              }
-
-            /* Record that we might have deleted mergeinfo */
-            if (!merge_b->paths_with_deleted_mergeinfo)
-              merge_b->paths_with_deleted_mergeinfo =
-                                                apr_hash_make(merge_b->pool);
-
-            apr_hash_set(merge_b->paths_with_deleted_mergeinfo,
-                         apr_pstrdup(merge_b->pool, local_abspath),
-                         APR_HASH_KEY_STRING, local_abspath);
-          }
-        else
-          {
-            svn_boolean_t moved_away;
-            svn_wc_conflict_reason_t reason;
+      /* 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));
+      *tree_conflicted = TRUE;
+      *state = svn_wc_notify_state_conflicted;
+    }
+  else
+    {
+      *state = svn_wc_notify_state_changed;
+    }
 
-            /* Dir is already not under version control at this path. */
-            /* Raise a tree conflict. */
-            SVN_ERR(check_moved_away(&moved_away, merge_b->ctx->wc_ctx,
-                                     local_abspath, scratch_pool));
-            reason = moved_away ? svn_wc_conflict_reason_moved_away
-                                : svn_wc_conflict_reason_deleted;
-            SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_dir,
-                                  svn_wc_conflict_action_delete, reason));
-            if (tree_conflicted)
-              *tree_conflicted = TRUE;
-          }
-      }
-      break;
-    case svn_node_file:
-      if (state)
-        *state = svn_wc_notify_state_obstructed;
-      break;
-    case svn_node_none:
-      {
-        svn_boolean_t moved_away;
-        svn_wc_conflict_reason_t reason;
+  /* Record that we might have deleted mergeinfo */
+  if (!merge_b->paths_with_deleted_mergeinfo)
+    merge_b->paths_with_deleted_mergeinfo =
+                                      apr_hash_make(merge_b->pool);
 
-        /* Dir is already non-existent. This is use case 6 as described in
-         * notes/tree-conflicts/detection.txt.
-         * This case was formerly treated as no-op. */
-        SVN_ERR(check_moved_away(&moved_away, merge_b->ctx->wc_ctx,
-                                 local_abspath, scratch_pool));
-        reason = moved_away ? svn_wc_conflict_reason_moved_away
-                            : svn_wc_conflict_reason_deleted;
-        SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_dir,
-                              svn_wc_conflict_action_delete, reason));
-        if (tree_conflicted)
-          *tree_conflicted = TRUE;
-        if (state)
-          *state = svn_wc_notify_state_missing;
-      }
-      break;
-    default:
-      if (state)
-        *state = svn_wc_notify_state_unknown;
-      break;
-    }
+  store_path(merge_b->paths_with_deleted_mergeinfo, local_abspath);
 
   return SVN_NO_ERROR;
 }
@@ -2746,8 +2478,7 @@ merge_dir_opened(svn_boolean_t *tree_con
   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
 
   /* Check for an obstructed or missing node on disk. */
-  SVN_ERR(perform_obstruction_check(&obstr_state, NULL,
-                                    &is_deleted, &wc_kind,
+  SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &wc_kind,
                                     merge_b, local_abspath, svn_node_unknown,
                                     scratch_pool));
 
@@ -2764,14 +2495,7 @@ merge_dir_opened(svn_boolean_t *tree_con
 
           if (is_wcroot)
             {
-              const char *skipped_path;
-
-              skipped_path = apr_pstrdup(apr_hash_pool_get(
-                                                  merge_b->skipped_abspaths),
-                                         local_abspath);
-
-              apr_hash_set(merge_b->skipped_abspaths, skipped_path,
-                           APR_HASH_KEY_STRING, skipped_path);
+              store_path(merge_b->skipped_abspaths, local_abspath);
 
               *skip = TRUE;
               *skip_children = TRUE;
@@ -2781,7 +2505,7 @@ merge_dir_opened(svn_boolean_t *tree_con
                   svn_wc_notify_t *notify;
 
                   notify = svn_wc_create_notify(
-                                        skipped_path,
+                                        local_abspath,
                                         svn_wc_notify_update_skip_obstruction,
                                         scratch_pool);
                   notify->kind = svn_node_dir;
@@ -2829,8 +2553,8 @@ merge_dir_opened(svn_boolean_t *tree_con
           SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_dir,
                                 svn_wc_conflict_action_edit,
                                 svn_wc_conflict_reason_replaced));
-          if (tree_conflicted)
-            *tree_conflicted = TRUE;
+          *tree_conflicted = TRUE;
+          *skip_children = TRUE;
         }
 
       /* If we're trying to open a directory that's locally deleted,
@@ -2844,17 +2568,24 @@ merge_dir_opened(svn_boolean_t *tree_con
        * forcing the user to sanity-check the merge result. */
       else if (is_deleted || wc_kind == svn_node_none)
         {
-          svn_boolean_t moved_away;
           svn_wc_conflict_reason_t reason;
 
-          SVN_ERR(check_moved_away(&moved_away, merge_b->ctx->wc_ctx,
-                                   local_abspath, scratch_pool));
-          reason = moved_away ? svn_wc_conflict_reason_moved_away
-                              : svn_wc_conflict_reason_deleted;
+          if (is_deleted)
+            {
+              svn_boolean_t moved_away;
+
+              SVN_ERR(check_moved_away(&moved_away, merge_b->ctx->wc_ctx,
+                                       local_abspath, scratch_pool));
+              reason = moved_away ? svn_wc_conflict_reason_moved_away
+                                  : svn_wc_conflict_reason_deleted;
+            }
+          else
+            reason = svn_wc_conflict_reason_missing;
+
           SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_dir,
                                 svn_wc_conflict_action_edit, reason));
-          if (tree_conflicted)
-            *tree_conflicted = TRUE;
+          *tree_conflicted = TRUE;
+          *skip_children = TRUE;
         }
     }
 
@@ -2935,7 +2666,7 @@ typedef struct notification_receiver_bat
 } notification_receiver_baton_t;
 
 
-/* Finds a nearest ancestor in CHILDREN_WITH_MERGEINFO for PATH. If
+/* Finds a nearest ancestor in CHILDREN_WITH_MERGEINFO for LOCAL_ABSPATH. 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.
@@ -2945,7 +2676,7 @@ typedef struct notification_receiver_bat
 static svn_client__merge_path_t *
 find_nearest_ancestor(const apr_array_header_t *children_with_mergeinfo,
                       svn_boolean_t path_is_own_ancestor,
-                      const char *path)
+                      const char *local_abspath)
 {
   int i;
   svn_client__merge_path_t *ancestor = NULL;
@@ -2956,9 +2687,9 @@ find_nearest_ancestor(const apr_array_he
     {
       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, path)
+      if (svn_dirent_is_ancestor(child->abspath, local_abspath)
           && (path_is_own_ancestor
-              || svn_path_compare_paths(child->abspath, path) != 0))
+              || strcmp(child->abspath, local_abspath) != 0))
         ancestor = child;
     }
   return ancestor;
@@ -3093,10 +2824,7 @@ update_the_list_of_added_subtrees(const 
 
   if (root_of_added_subtree)
     {
-      const char *added_root_path = apr_pstrdup(result_pool,
-                                                added_abspath);
-      apr_hash_set(*added_abspaths, added_root_path,
-                   APR_HASH_KEY_STRING, added_root_path);
+      store_path(*added_abspaths, added_abspath);
     }
 }
 
@@ -3191,30 +2919,17 @@ notification_receiver(void *baton, const
           || notify->prop_state == svn_wc_notify_state_changed
           || notify->action == svn_wc_notify_update_add)
         {
-          const char *merged_path = apr_pstrdup(notify_b->pool,
-                                                notify_abspath);
-
-          apr_hash_set(merge_b->merged_abspaths, merged_path,
-                       APR_HASH_KEY_STRING, merged_path);
+          store_path(merge_b->merged_abspaths, notify_abspath);
         }
 
       if (notify->action == svn_wc_notify_skip)
         {
-          const char *skipped_path = apr_pstrdup(notify_b->pool,
-                                                 notify_abspath);
-
-          apr_hash_set(merge_b->skipped_abspaths, skipped_path,
-                       APR_HASH_KEY_STRING, skipped_path);
+          store_path(merge_b->skipped_abspaths, notify_abspath);
         }
 
       if (notify->action == svn_wc_notify_tree_conflict)
         {
-          const char *tree_conflicted_path = apr_pstrdup(notify_b->pool,
-                                                         notify_abspath);
-
-          apr_hash_set(merge_b->tree_conflicted_abspaths,
-                       tree_conflicted_path, APR_HASH_KEY_STRING,
-                       tree_conflicted_path);
+          store_path(merge_b->tree_conflicted_abspaths, notify_abspath);
         }
 
       if (notify->action == svn_wc_notify_update_add)
@@ -4982,8 +4697,7 @@ record_skips(const char *mergeinfo_path,
 
       /* 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,
+      SVN_ERR(perform_obstruction_check(&obstruction_state, NULL, NULL,
                                         merge_b, skipped_abspath,
                                         svn_node_unknown, iterpool));
       if (obstruction_state == svn_wc_notify_state_obstructed
@@ -6636,12 +6350,8 @@ combine_range_with_segments(apr_array_he
   return SVN_NO_ERROR;
 }
 
-/* Similar to normalize_merge_sources() but:
- * no SOURCE_PATH_OR_URL argument;
- * MERGE_RANGE_TS (array of svn_merge_range_t *) instead of RANGES;
- * SOURCE_PEG_REVNUM instead of SOURCE_PEG_REVISION.
- * RA_SESSION is an RA session open to the repository of SOURCE_LOC; it may
- * be temporarily reparented within this function.
+/* Similar to normalize_merge_sources() except the input MERGE_RANGE_TS is a
+ * rangelist.
  */
 static svn_error_t *
 normalize_merge_sources_internal(apr_array_header_t **merge_sources_p,
@@ -6702,15 +6412,15 @@ normalize_merge_sources_internal(apr_arr
   trim_revision = SVN_INVALID_REVNUM;
   if (segments->nelts)
     {
-      svn_location_segment_t *segment =
+      svn_location_segment_t *first_segment =
         APR_ARRAY_IDX(segments, 0, svn_location_segment_t *);
 
       /* If the first segment doesn't start with the OLDEST_REQUESTED
          revision, we'll need to pass a trim revision to our range
          cruncher. */
-      if (segment->range_start != oldest_requested)
+      if (first_segment->range_start != oldest_requested)
         {
-          trim_revision = segment->range_start;
+          trim_revision = first_segment->range_start;
         }
 
       /* Else, if the first segment has no path (and therefore is a
@@ -6724,21 +6434,21 @@ normalize_merge_sources_internal(apr_arr
          ### really penalize clients hitting pre-1.5 repositories with
          ### the typical small merge range request (because of the
          ### lack of a node-origins cache in the repository).  */
-      else if (! segment->path)
+      else if (! first_segment->path)
         {
           if (segments->nelts > 1)
             {
-              svn_location_segment_t *segment2 =
+              svn_location_segment_t *second_segment =
                 APR_ARRAY_IDX(segments, 1, svn_location_segment_t *);
               const char *segment_url;
               const char *original_repos_relpath;
               svn_revnum_t original_revision;
               svn_opt_revision_t range_start_rev;
               range_start_rev.kind = svn_opt_revision_number;
-              range_start_rev.value.number = segment2->range_start;
+              range_start_rev.value.number = second_segment->range_start;
 
               segment_url = svn_path_url_add_component2(
-                              source_loc->repos_root_url, segment2->path,
+                              source_loc->repos_root_url, second_segment->path,
                               scratch_pool);
               SVN_ERR(svn_client__get_copy_source(&original_repos_relpath,
                                                   &original_revision,
@@ -6772,14 +6482,11 @@ normalize_merge_sources_internal(apr_arr
 
       if (SVN_IS_VALID_REVNUM(trim_revision))
         {
-          /* If the youngest of the range revisions predates the trim
-             revision, discard the range. */
+          /* If the range predates the trim revision, discard it. */
           if (MAX(range->start, range->end) < trim_revision)
             continue;
 
-          /* Otherwise, if either of oldest of the range revisions predates
-             the trim revision, update the range revision to be equal
-             to the trim revision. */
+          /* If the range overlaps the trim revision, trim it. */
           if (range->start < trim_revision)
             range->start = trim_revision;
           if (range->end < trim_revision)
@@ -6796,24 +6503,24 @@ normalize_merge_sources_internal(apr_arr
   return SVN_NO_ERROR;
 }
 
-/* Set *MERGE_SOURCES_P to an array of merge_source_t * objects, each
-   holding the paths and revisions needed to fully describe a range of
-   requested merges; order the objects from oldest to youngest.
-
-   Determine the requested merges by examining SOURCE_PATH_OR_URL (and its
-   associated URL and revision, SOURCE_LOC) (which
-   specifies the line of history from which merges will be pulled) and
-   RANGES_TO_MERGE (a list of svn_opt_revision_range_t's which provide
-   revision ranges).
+/* Determine the normalized ranges to merge from a given line of history.
 
-   RA_SESSION is an RA session open to the repository of SOURCE_LOC; it may
-   be temporarily reparented within this function.  Use RA_SESSION to answer
-   historical questions.
+   Calculate the result by intersecting the list of location segments at
+   which SOURCE_LOC existed along its line of history with the requested
+   revision ranges in RANGES_TO_MERGE.  RANGES_TO_MERGE is an array of
+   (svn_opt_revision_range_t *) revision ranges.  Use SOURCE_PATH_OR_URL to
+   resolve any WC-relative revision specifiers (such as 'base') in
+   RANGES_TO_MERGE.
+
+   Set *MERGE_SOURCES_P to an array of merge_source_t * objects, each
+   describing a normalized range of revisions to be merged from the line
+   history of SOURCE_LOC.  Order the objects from oldest to youngest.
 
-   CTX is a client context baton.
+   RA_SESSION is an RA session open to the repository of SOURCE_LOC; it may
+   be temporarily reparented within this function.  Use RA_SESSION to find
+   the location segments along the line of history of SOURCE_LOC.
 
-   SCRATCH_POOL is used for all temporary allocations.  MERGE_SOURCES_P and
-   its contents are allocated in RESULT_POOL.
+   Allocate MERGE_SOURCES_P and its contents in RESULT_POOL.
 
    See `MERGEINFO MERGE SOURCE NORMALIZATION' for more on the
    background of this function.
@@ -6822,7 +6529,7 @@ static svn_error_t *
 normalize_merge_sources(apr_array_header_t **merge_sources_p,
                         const char *source_path_or_url,
                         const svn_client__pathrev_t *source_loc,
-                        const svn_rangelist_t *ranges_to_merge,
+                        const apr_array_header_t *ranges_to_merge,
                         svn_ra_session_t *ra_session,
                         svn_client_ctx_t *ctx,
                         apr_pool_t *result_pool,
@@ -7127,8 +6834,9 @@ do_file_merge(svn_mergeinfo_catalog_t re
           svn_string_t *pval;
           const char *mimetype1, *mimetype2;
           apr_array_header_t *propchanges;
-          svn_wc_notify_state_t prop_state, text_state;
-          svn_boolean_t tree_conflicted = TRUE;
+          svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
+          svn_wc_notify_state_t text_state = svn_wc_notify_state_unknown;
+          svn_boolean_t tree_conflicted = FALSE;
 
           svn_pool_clear(iterpool);
 
@@ -7159,11 +6867,8 @@ do_file_merge(svn_mergeinfo_catalog_t re
           /* Deduce property diffs. */
           SVN_ERR(svn_prop_diffs(&propchanges, props2, props1, iterpool));
 
-          /* If we aren't ignoring ancestry, then we've already done
-             ancestry relatedness checks.  If we are ignoring ancestry, or
-             our sources are known to be related, then we can do
-             text-n-props merge; otherwise, we have to do a delete-n-add
-             merge.  */
+          /* If the sources are related or we're ignoring ancestry in diffs,
+             do a text-n-props merge; otherwise, do a delete-n-add merge. */
           if (! (merge_b->ignore_ancestry || sources_related))
             {
               /* Delete... */
@@ -8471,8 +8176,7 @@ log_noop_revs(void *baton,
 
           if (cwmi_abspath[0] == '\0'
               || svn_dirent_is_root(cwmi_abspath, strlen(cwmi_abspath))
-              || svn_path_compare_paths(log_gap_baton->target->abspath,
-                                        cwmi_abspath) == 0)
+              || strcmp(log_gap_baton->target->abspath, cwmi_abspath) == 0)
             {
               /* Can't crawl any higher. */
               break;
@@ -9130,8 +8834,8 @@ ensure_ra_session_url(svn_ra_session_t *
    and possibly record mergeinfo describing the merge -- see
    RECORD_MERGEINFO().
 
-   If MODIFIED_SUBTREES is not NULL and SOURCES_ANCESTRAL or
-   REINTEGRATE_MERGE is true, then replace *MODIFIED_SUBTREES with a new
+   If MODIFIED_SUBTREES is not NULL and all the MERGE_SOURCES are 'ancestral'
+   or REINTEGRATE_MERGE is true, then replace *MODIFIED_SUBTREES with a new
    hash containing all the paths that *MODIFIED_SUBTREES contained before,
    and also every path modified, skipped, added, or tree-conflicted
    by the merge.  Keys and values of the hash are both (const char *)
@@ -9164,9 +8868,23 @@ ensure_ra_session_url(svn_ra_session_t *
    paths and the values are the new mergeinfos for each.  Allocate additions
    to RESULT_CATALOG in pool which RESULT_CATALOG was created in.
 
-   FORCE, DRY_RUN, RECORD_ONLY, IGNORE_ANCESTRY, DEPTH, MERGE_OPTIONS,
+   FORCE_DELETE, DRY_RUN, RECORD_ONLY, DEPTH, MERGE_OPTIONS,
    and CTX are as described in the docstring for svn_client_merge_peg3().
 
+   IGNORE_ANCESTRY has both of the following meanings:
+
+     (1) Disable merge tracking, by treating the two sources as unrelated
+     even if they actually have a common ancestor.  See the macro
+     HONOR_MERGEINFO().
+
+     (2) Diff unrelated nodes as if related.  If IGNORE_ANCESTRY is true,
+     the 'left' and 'right' versions of a node (if they are the same kind)
+     will be diffed as if they were related even if they are not related.
+     Otherwise, unrelated items will be diffed as a deletion of one thing
+     and the addition of another.
+
+   ### TODO: Use separate flags for the two meanings.
+
    If not NULL, RECORD_ONLY_PATHS is a hash of (const char *) paths mapped
    to the same.  If RECORD_ONLY is true and RECORD_ONLY_PATHS is not NULL,
    then record mergeinfo describing the merge only on subtrees which contain
@@ -9190,7 +8908,7 @@ do_merge(apr_hash_t **modified_subtrees,
          svn_boolean_t sources_related,
          svn_boolean_t same_repos,
          svn_boolean_t ignore_ancestry,
-         svn_boolean_t force,
+         svn_boolean_t force_delete,
          svn_boolean_t dry_run,
          svn_boolean_t record_only,
          apr_hash_t *record_only_paths,
@@ -9269,7 +8987,7 @@ do_merge(apr_hash_t **modified_subtrees,
 
   /* Build the merge context baton (or at least the parts of it that
      don't need to be reset for each merge source).  */
-  merge_cmd_baton.force = force;
+  merge_cmd_baton.force_delete = force_delete;
   merge_cmd_baton.dry_run = dry_run;
   merge_cmd_baton.record_only = record_only;
   merge_cmd_baton.ignore_ancestry = ignore_ancestry;
@@ -9313,6 +9031,7 @@ do_merge(apr_hash_t **modified_subtrees,
 
   for (i = 0; i < merge_sources->nelts; i++)
     {
+      svn_node_kind_t src1_kind;
       merge_source_t *source =
         APR_ARRAY_IDX(merge_sources, i, merge_source_t *);
 
@@ -9334,12 +9053,10 @@ do_merge(apr_hash_t **modified_subtrees,
          be reset for each merge source iteration. */
       merge_cmd_baton.merge_source = *source;
       merge_cmd_baton.implicit_src_gap = NULL;
-      merge_cmd_baton.add_necessitated_merge = FALSE;
       merge_cmd_baton.dry_run_deletions =
         dry_run ? apr_hash_make(iterpool) : NULL;
       merge_cmd_baton.dry_run_added =
         dry_run ? apr_hash_make(iterpool) : NULL;
-      merge_cmd_baton.dry_run_last_added_dir = NULL;
       merge_cmd_baton.conflicted_paths = NULL;
       merge_cmd_baton.paths_with_new_mergeinfo = NULL;
       merge_cmd_baton.paths_with_deleted_mergeinfo = NULL;
@@ -9357,8 +9074,11 @@ do_merge(apr_hash_t **modified_subtrees,
           checked_mergeinfo_capability = TRUE;
         }
 
-      /* Call our merge helpers based on TARGET's kind. */
-      if (target->kind == svn_node_file)
+      SVN_ERR(svn_ra_check_path(ra_session1, "", source->loc1->rev,
+                                &src1_kind, iterpool));
+
+      /* Call our merge helpers based on SRC1's kind. */
+      if (src1_kind != svn_node_dir)
         {
           SVN_ERR(do_file_merge(result_catalog,
                                 source, target->abspath,
@@ -9367,7 +9087,7 @@ do_merge(apr_hash_t **modified_subtrees,
                                 &notify_baton,
                                 &merge_cmd_baton, iterpool));
         }
-      else if (target->kind == svn_node_dir)
+      else /* Directory */
         {
           /* If conflicts occur while merging any but the very last
            * revision range we want an error to be raised that aborts
@@ -9429,6 +9149,9 @@ do_merge(apr_hash_t **modified_subtrees,
    repository as the target working copy.  Other arguments are as in
    all of the public merge APIs.
 
+   IGNORE_ANCESTRY has two meanings: see do_merge().
+   ### TODO: Use separate flags for the two meanings.
+
    *USE_SLEEP will be set TRUE if a sleep is required to ensure timestamp
    integrity, *USE_SLEEP will be unchanged if no sleep is required.
 
@@ -9443,7 +9166,7 @@ merge_cousins_and_supplement_mergeinfo(c
                                        svn_boolean_t same_repos,
                                        svn_depth_t depth,
                                        svn_boolean_t ignore_ancestry,
-                                       svn_boolean_t force,
+                                       svn_boolean_t force_delete,
                                        svn_boolean_t record_only,
                                        svn_boolean_t dry_run,
                                        const apr_array_header_t *merge_options,
@@ -9488,7 +9211,7 @@ merge_cousins_and_supplement_mergeinfo(c
       APR_ARRAY_PUSH(faux_sources, const merge_source_t *) = source;
       SVN_ERR(do_merge(&modified_subtrees, NULL, faux_sources, target,
                        URL1_ra_session, TRUE, same_repos,
-                       ignore_ancestry, force, dry_run, FALSE, NULL, TRUE,
+                       ignore_ancestry, force_delete, dry_run, FALSE, NULL, TRUE,
                        FALSE, depth, merge_options, use_sleep, ctx,
                        scratch_pool, subpool));
     }
@@ -9520,14 +9243,14 @@ merge_cousins_and_supplement_mergeinfo(c
       svn_pool_clear(subpool);
       SVN_ERR(do_merge(NULL, add_result_catalog, add_sources, target,
                        URL1_ra_session, TRUE, same_repos,
-                       ignore_ancestry, force, dry_run, TRUE,
+                       ignore_ancestry, force_delete, dry_run, TRUE,
                        modified_subtrees, TRUE,
                        TRUE, depth, merge_options, use_sleep, ctx,
                        scratch_pool, subpool));
       svn_pool_clear(subpool);
       SVN_ERR(do_merge(NULL, remove_result_catalog, remove_sources, target,
                        URL1_ra_session, TRUE, same_repos,
-                       ignore_ancestry, force, dry_run, TRUE,
+                       ignore_ancestry, force_delete, dry_run, TRUE,
                        modified_subtrees, TRUE,
                        TRUE, depth, merge_options, use_sleep, ctx,
                        scratch_pool, subpool));
@@ -9724,7 +9447,15 @@ open_target_wc(merge_target_t **target_p
 
 /*** Public APIs ***/
 
-/* The body of svn_client_merge4(), which see for details. */
+/* The body of svn_client_merge4(), which see for details.
+ *
+ * If SOURCE1 @ REVISION1 is related to SOURCE2 @ REVISION2 then use merge
+ * tracking (subject to other constraints -- see HONOR_MERGEINFO());
+ * otherwise disable merge tracking.
+ *
+ * IGNORE_ANCESTRY has two meanings: see do_merge().
+ * ### TODO: Use separate flags for the two meanings.
+ */
 static svn_error_t *
 merge_locked(const char *source1,
              const svn_opt_revision_t *revision1,
@@ -9733,7 +9464,7 @@ merge_locked(const char *source1,
              const char *target_abspath,
              svn_depth_t depth,
              svn_boolean_t ignore_ancestry,
-             svn_boolean_t force,
+             svn_boolean_t force_delete,
              svn_boolean_t record_only,
              svn_boolean_t dry_run,
              svn_boolean_t allow_mixed_rev,
@@ -9743,7 +9474,7 @@ merge_locked(const char *source1,
 {
   merge_target_t *target;
   svn_client__pathrev_t *source1_loc, *source2_loc;
-  svn_boolean_t related = FALSE;
+  svn_boolean_t sources_related = FALSE;
   svn_ra_session_t *ra_session1, *ra_session2;
   apr_array_header_t *merge_sources;
   svn_error_t *err;
@@ -9809,7 +9540,7 @@ merge_locked(const char *source1,
   if (yca)
     {
       /* Note that our merge sources are related. */
-      related = TRUE;
+      sources_related = TRUE;
 
       /* If the common ancestor matches the right side of our merge,
          then we only need to reverse-merge the left side. */
@@ -9850,7 +9581,8 @@ merge_locked(const char *source1,
                                                        yca,
                                                        same_repos,
                                                        depth,
-                                                       ignore_ancestry, force,
+                                                       ignore_ancestry,
+                                                       force_delete,
                                                        record_only, dry_run,
                                                        merge_options,
                                                        &use_sleep, ctx,
@@ -9885,8 +9617,8 @@ merge_locked(const char *source1,
     }
 
   err = do_merge(NULL, NULL, merge_sources, target,
-                 ra_session1, related, same_repos,
-                 ignore_ancestry, force, dry_run,
+                 ra_session1, sources_related, same_repos,
+                 ignore_ancestry, force_delete, dry_run,
                  record_only, NULL, FALSE, FALSE, depth, merge_options,
                  &use_sleep, ctx, scratch_pool, scratch_pool);
 
@@ -9932,7 +9664,7 @@ svn_client_merge4(const char *source1,
                   const char *target_wcpath,
                   svn_depth_t depth,
                   svn_boolean_t ignore_ancestry,
-                  svn_boolean_t force,
+                  svn_boolean_t force_delete,
                   svn_boolean_t record_only,
                   svn_boolean_t dry_run,
                   svn_boolean_t allow_mixed_rev,
@@ -9965,13 +9697,13 @@ svn_client_merge4(const char *source1,
     SVN_WC__CALL_WITH_WRITE_LOCK(
       merge_locked(source1, revision1, source2, revision2,
                    target_abspath, depth, ignore_ancestry,
-                   force, record_only, dry_run,
+                   force_delete, record_only, dry_run,
                    allow_mixed_rev, merge_options, ctx, pool),
       ctx->wc_ctx, lock_abspath, FALSE /* lock_anchor */, pool);
   else
     SVN_ERR(merge_locked(source1, revision1, source2, revision2,
                    target_abspath, depth, ignore_ancestry,
-                   force, record_only, dry_run,

[... 135 lines stripped ...]