You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by rh...@apache.org on 2013/01/29 23:26:58 UTC

svn commit: r1440188 - /subversion/trunk/subversion/libsvn_client/merge.c

Author: rhuijben
Date: Tue Jan 29 22:26:58 2013
New Revision: 1440188

URL: http://svn.apache.org/viewvc?rev=1440188&view=rev
Log:
Move a few functions in merge.c to a more sensible position, given that they
will be updated to the tree processor code in the near future.

First: Open, Change, Add, Delete, Close for files.
And then: Open, Change, Add, Delete, Close for directories.

No functional changes.

* subversion/libsvn_client/merge.c
  (merge_dir_props_changed): Move between the directory functions.
  (merge_dir_opened): Insert here.
  (merge_dir_props_changed): Insert here.
  (merge_dir_opened): Remove here.

Modified:
    subversion/trunk/subversion/libsvn_client/merge.c

Modified: subversion/trunk/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/merge.c?rev=1440188&r1=1440187&r2=1440188&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/merge.c (original)
+++ subversion/trunk/subversion/libsvn_client/merge.c Tue Jan 29 22:26:58 2013
@@ -1678,184 +1678,6 @@ record_update_delete(merge_cmd_baton_t *
 
 /* An svn_wc_diff_callbacks4_t function. */
 static svn_error_t *
-merge_dir_props_changed(svn_wc_notify_state_t *state,
-                        svn_boolean_t *tree_conflicted,
-                        const char *local_relpath,
-                        svn_boolean_t dir_was_added,
-                        const apr_array_header_t *propchanges,
-                        apr_hash_t *original_props,
-                        void *diff_baton,
-                        apr_pool_t *scratch_pool)
-{
-  merge_cmd_baton_t *merge_b = diff_baton;
-  const apr_array_header_t *props;
-  const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
-                                              local_relpath, scratch_pool);
-  svn_wc_notify_state_t obstr_state;
-  svn_boolean_t is_deleted;
-  svn_node_kind_t 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)
-    {
-      *state = obstr_state;
-      SVN_ERR(record_skip(merge_b, local_abspath, svn_node_dir,
-                          obstr_state, scratch_pool));
-      return SVN_NO_ERROR;
-    }
-
-  if (kind != svn_node_dir || is_deleted)
-    {
-      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_file,
-                            svn_wc_conflict_action_edit, reason));
-
-      *tree_conflicted = TRUE;
-      *state = svn_wc_notify_state_missing;
-      SVN_ERR(record_tree_conflict(merge_b, local_abspath, svn_node_dir,
-                                   scratch_pool));
-
-      return SVN_NO_ERROR;
-    }
-
-  if (dir_was_added
-      && merge_b->dry_run
-      && dry_run_added_p(merge_b, local_abspath))
-    {
-      return SVN_NO_ERROR; /* We can't do a real prop merge for added dirs */
-    }
-
-  if (dir_was_added && merge_b->same_repos)
-    {
-      /* When the directory was added in merge_dir_added() we didn't update its
-         pristine properties. Instead we receive the property changes later and
-         apply them in this function.
-
-         If we would apply them as changes (such as before fixing issue #3405),
-         we would see the unmodified properties as local changes, and the
-         pristine properties would be out of sync with what the repository
-         expects for this directory.
-
-         Instead of doing that we now simply set the properties as the pristine
-         properties via a private libsvn_wc api.
-      */
-
-      const char *copyfrom_url;
-      svn_revnum_t copyfrom_rev;
-      const char *parent_abspath;
-      const char *child;
-
-      /* Creating a hash containing regular and entry props */
-      apr_hash_t *new_pristine_props = svn_prop__patch(original_props, propchanges,
-                                                       scratch_pool);
-
-      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 = merge_b->merge_source.loc2->rev;
-
-      SVN_ERR(check_repos_match(merge_b->target, parent_abspath, copyfrom_url,
-                                scratch_pool));
-
-      if (!merge_b->dry_run)
-        {
-          SVN_ERR(svn_wc__complete_directory_add(merge_b->ctx->wc_ctx,
-                                                local_abspath,
-                                                new_pristine_props,
-                                                copyfrom_url, copyfrom_rev,
-                                                scratch_pool));
-        }
-      *state = svn_wc_notify_state_changed; /* same as merge_props did */
-
-      /* Until issue #3405 was fixed the code for changed directories was
-         used for directory deletions, which made use apply svn:mergeinfo as if
-         additional changes were applied by the merge, which really weren't.
-
-         ### I wouldn't be surprised if we somehow relied on these for correct
-         ### merges, but in this case the fix added here should also be applied
-         ### for added files! */
-
-      /* ### The old code performed (via prepare_merge_props_changed):
-      if (apr_hash_get(new_pristine_props, SVN_PROP_MERGEINFO,
-                       APR_HASH_KEY_STRING))
-        {
-          alloc_and_store_path(&merge_b->paths_with_new_mergeinfo,
-                               local_abspath, merge_b->pool);
-        }
-         ### which is something merge_add_file() doesn't do, but
-         ### merge_tests.py 95: no self referential filtering on added path
-         ### fails if this block is not enabled.
-      */
-
-      return SVN_NO_ERROR;
-    }
-
-  SVN_ERR(prepare_merge_props_changed(&props, local_abspath, propchanges,
-                                      merge_b, scratch_pool, scratch_pool));
-
-  if (props->nelts)
-    {
-      const svn_wc_conflict_version_t *left;
-      const svn_wc_conflict_version_t *right;
-      svn_client_ctx_t *ctx = merge_b->ctx;
-
-      SVN_ERR(make_conflict_versions(&left, &right, local_abspath,
-                                     svn_node_dir, &merge_b->merge_source,
-                                     merge_b->target, merge_b->pool));
-
-      SVN_ERR(svn_wc_merge_props3(state, ctx->wc_ctx, local_abspath,
-                                  left, right,
-                                  original_props, props,
-                                  merge_b->dry_run,
-                                  ctx->conflict_func2, ctx->conflict_baton2,
-                                  ctx->cancel_func, ctx->cancel_baton,
-                                  scratch_pool));
-
-      if (*state == svn_wc_notify_state_conflicted)
-        {
-          alloc_and_store_path(&merge_b->conflicted_paths, local_abspath,
-                               merge_b->pool);
-        }
-
-      if (*state == svn_wc_notify_state_conflicted
-          || *state == svn_wc_notify_state_merged
-          || *state == svn_wc_notify_state_changed)
-        {
-          SVN_ERR(record_update_update(merge_b, local_abspath, svn_node_file,
-                                       svn_wc_notify_state_inapplicable,
-                                       *state, scratch_pool));
-        }
-    }
-  else
-    *state = svn_wc_notify_state_unchanged;
-
-  return SVN_NO_ERROR;
-}
-
-/* An svn_wc_diff_callbacks4_t function. */
-static svn_error_t *
 merge_file_opened(svn_boolean_t *tree_conflicted,
                   svn_boolean_t *skip,
                   const char *path,
@@ -2536,68 +2358,386 @@ merge_file_deleted(svn_wc_notify_state_t
 
 /* An svn_wc_diff_callbacks4_t function. */
 static svn_error_t *
-merge_dir_added(svn_wc_notify_state_t *state,
-                svn_boolean_t *tree_conflicted,
-                svn_boolean_t *skip,
-                svn_boolean_t *skip_children,
-                const char *local_relpath,
-                svn_revnum_t rev,
-                const char *copyfrom_path,
-                svn_revnum_t copyfrom_revision,
-                void *baton,
-                apr_pool_t *scratch_pool)
+merge_dir_opened(svn_boolean_t *tree_conflicted,
+                 svn_boolean_t *skip,
+                 svn_boolean_t *skip_children,
+                 const char *local_relpath,
+                 svn_revnum_t rev,
+                 void *baton,
+                 apr_pool_t *scratch_pool)
 {
   merge_cmd_baton_t *merge_b = baton;
   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;
-  svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
+  svn_node_kind_t wc_kind;
+  svn_wc_notify_state_t obstr_state;
   svn_boolean_t is_deleted;
-  svn_boolean_t add_existing = FALSE;
 
-  /* Easy out: We are only applying mergeinfo differences. */
-  if (merge_b->record_only)
-    {
-      *state = svn_wc_notify_state_unchanged;
-      return SVN_NO_ERROR;
-    }
+  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, &is_deleted, &wc_kind,
+                                    merge_b, local_abspath, svn_node_unknown,
+                                    scratch_pool));
 
-  /* 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)
+  if (obstr_state != svn_wc_notify_state_inapplicable)
     {
-      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);
+      /* In Subversion <= 1.7 we always skipped descendants here */
+      if (obstr_state == svn_wc_notify_state_obstructed)
+        {
+          svn_boolean_t is_wcroot;
 
-      copyfrom_url = svn_path_url_add_component2(merge_b->merge_source.loc2->url,
-                                                 child, scratch_pool);
-      copyfrom_rev = rev;
+          SVN_ERR(svn_wc_check_root(&is_wcroot, NULL, NULL,
+                                  merge_b->ctx->wc_ctx,
+                                  local_abspath, scratch_pool));
 
-      SVN_ERR(check_repos_match(merge_b->target, parent_abspath, copyfrom_url,
-                                scratch_pool));
-    }
+          if (is_wcroot)
+            {
+              store_path(merge_b->skipped_abspaths, local_abspath);
 
-  /* Check for an obstructed or missing node on disk. */
-  {
-    svn_wc_notify_state_t obstr_state;
+              *skip = TRUE;
+              *skip_children = TRUE;
 
-    SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &kind,
-                                      merge_b, local_abspath, svn_node_unknown,
-                                      scratch_pool));
+              if (merge_b->ctx->notify_func2)
+                {
+                  svn_wc_notify_t *notify;
 
-    /* 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
-     * risk of destroying data. Only skip if it is versioned but unexpectedly
-     * missing from disk, or is unversioned but obstructed by a node of the
-     * wrong kind. */
+                  notify = svn_wc_create_notify(
+                                        local_abspath,
+                                        svn_wc_notify_update_skip_obstruction,
+                                        scratch_pool);
+                  notify->kind = svn_node_dir;
+                  notify->content_state = obstr_state;
+                  merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2,
+                                             notify, scratch_pool);
+                }
+            }
+        }
+
+      return SVN_NO_ERROR;
+    }
+
+  if (wc_kind != svn_node_dir || is_deleted)
+    {
+      if (wc_kind == svn_node_none)
+        {
+          svn_depth_t parent_depth;
+
+          /* If the parent is too shallow to contain this directory,
+           * and the directory is not present on disk, skip it.
+           * Non-inheritable mergeinfo will be recorded, allowing
+           * future merges into non-shallow working copies to merge
+           * changes we missed this time around. */
+          SVN_ERR(svn_wc__node_get_depth(&parent_depth, merge_b->ctx->wc_ctx,
+                                         svn_dirent_dirname(local_abspath,
+                                                            scratch_pool),
+                                         scratch_pool));
+          if (parent_depth != svn_depth_unknown &&
+              parent_depth < svn_depth_immediates)
+            {
+              /* In Subversion <= 1.7 we skipped descendants here */
+              return SVN_NO_ERROR;
+            }
+        }
+
+      /* Check for tree conflicts, if any. */
+
+      /* If we're trying to open a file, the reason for the conflict is
+       * 'replaced'. Because the merge is trying to open the directory,
+       * rather than adding it, the directory must have existed in the
+       * history of the target branch and has been replaced with a file. */
+      if (wc_kind == svn_node_file)
+        {
+          SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_dir,
+                                svn_wc_conflict_action_edit,
+                                svn_wc_conflict_reason_replaced));
+          *tree_conflicted = TRUE;
+          *skip_children = TRUE;
+          SVN_ERR(record_tree_conflict(merge_b, local_abspath, svn_node_dir,
+                                       scratch_pool));
+        }
+
+      /* If we're trying to open a directory that's locally deleted,
+       * or not present because it was deleted in the history of the
+       * target branch, the reason for the conflict is 'deleted'.
+       *
+       * If the DB says something should be here, but there is
+       * nothing on disk, we're probably in a mixed-revision
+       * working copy and the parent has an outdated idea about
+       * the state of its child. Flag a tree conflict in this case
+       * forcing the user to sanity-check the merge result. */
+      else if (is_deleted || wc_kind == svn_node_none)
+        {
+          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));
+              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));
+          *tree_conflicted = TRUE;
+          *skip = TRUE;
+          *skip_children = TRUE;
+          SVN_ERR(record_tree_conflict(merge_b, local_abspath, svn_node_dir,
+                                       scratch_pool));
+        }
+    }
+
+  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,
+                        svn_boolean_t *tree_conflicted,
+                        const char *local_relpath,
+                        svn_boolean_t dir_was_added,
+                        const apr_array_header_t *propchanges,
+                        apr_hash_t *original_props,
+                        void *diff_baton,
+                        apr_pool_t *scratch_pool)
+{
+  merge_cmd_baton_t *merge_b = diff_baton;
+  const apr_array_header_t *props;
+  const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
+                                              local_relpath, scratch_pool);
+  svn_wc_notify_state_t obstr_state;
+  svn_boolean_t is_deleted;
+  svn_node_kind_t 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)
+    {
+      *state = obstr_state;
+      SVN_ERR(record_skip(merge_b, local_abspath, svn_node_dir,
+                          obstr_state, scratch_pool));
+      return SVN_NO_ERROR;
+    }
+
+  if (kind != svn_node_dir || is_deleted)
+    {
+      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_file,
+                            svn_wc_conflict_action_edit, reason));
+
+      *tree_conflicted = TRUE;
+      *state = svn_wc_notify_state_missing;
+      SVN_ERR(record_tree_conflict(merge_b, local_abspath, svn_node_dir,
+                                   scratch_pool));
+
+      return SVN_NO_ERROR;
+    }
+
+  if (dir_was_added
+      && merge_b->dry_run
+      && dry_run_added_p(merge_b, local_abspath))
+    {
+      return SVN_NO_ERROR; /* We can't do a real prop merge for added dirs */
+    }
+
+  if (dir_was_added && merge_b->same_repos)
+    {
+      /* When the directory was added in merge_dir_added() we didn't update its
+         pristine properties. Instead we receive the property changes later and
+         apply them in this function.
+
+         If we would apply them as changes (such as before fixing issue #3405),
+         we would see the unmodified properties as local changes, and the
+         pristine properties would be out of sync with what the repository
+         expects for this directory.
+
+         Instead of doing that we now simply set the properties as the pristine
+         properties via a private libsvn_wc api.
+      */
+
+      const char *copyfrom_url;
+      svn_revnum_t copyfrom_rev;
+      const char *parent_abspath;
+      const char *child;
+
+      /* Creating a hash containing regular and entry props */
+      apr_hash_t *new_pristine_props = svn_prop__patch(original_props, propchanges,
+                                                       scratch_pool);
+
+      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 = merge_b->merge_source.loc2->rev;
+
+      SVN_ERR(check_repos_match(merge_b->target, parent_abspath, copyfrom_url,
+                                scratch_pool));
+
+      if (!merge_b->dry_run)
+        {
+          SVN_ERR(svn_wc__complete_directory_add(merge_b->ctx->wc_ctx,
+                                                local_abspath,
+                                                new_pristine_props,
+                                                copyfrom_url, copyfrom_rev,
+                                                scratch_pool));
+        }
+      *state = svn_wc_notify_state_changed; /* same as merge_props did */
+
+      /* Until issue #3405 was fixed the code for changed directories was
+         used for directory deletions, which made use apply svn:mergeinfo as if
+         additional changes were applied by the merge, which really weren't.
+
+         ### I wouldn't be surprised if we somehow relied on these for correct
+         ### merges, but in this case the fix added here should also be applied
+         ### for added files! */
+
+      /* ### The old code performed (via prepare_merge_props_changed):
+      if (apr_hash_get(new_pristine_props, SVN_PROP_MERGEINFO,
+                       APR_HASH_KEY_STRING))
+        {
+          alloc_and_store_path(&merge_b->paths_with_new_mergeinfo,
+                               local_abspath, merge_b->pool);
+        }
+         ### which is something merge_add_file() doesn't do, but
+         ### merge_tests.py 95: no self referential filtering on added path
+         ### fails if this block is not enabled.
+      */
+
+      return SVN_NO_ERROR;
+    }
+
+  SVN_ERR(prepare_merge_props_changed(&props, local_abspath, propchanges,
+                                      merge_b, scratch_pool, scratch_pool));
+
+  if (props->nelts)
+    {
+      const svn_wc_conflict_version_t *left;
+      const svn_wc_conflict_version_t *right;
+      svn_client_ctx_t *ctx = merge_b->ctx;
+
+      SVN_ERR(make_conflict_versions(&left, &right, local_abspath,
+                                     svn_node_dir, &merge_b->merge_source,
+                                     merge_b->target, merge_b->pool));
+
+      SVN_ERR(svn_wc_merge_props3(state, ctx->wc_ctx, local_abspath,
+                                  left, right,
+                                  original_props, props,
+                                  merge_b->dry_run,
+                                  ctx->conflict_func2, ctx->conflict_baton2,
+                                  ctx->cancel_func, ctx->cancel_baton,
+                                  scratch_pool));
+
+      if (*state == svn_wc_notify_state_conflicted)
+        {
+          alloc_and_store_path(&merge_b->conflicted_paths, local_abspath,
+                               merge_b->pool);
+        }
+
+      if (*state == svn_wc_notify_state_conflicted
+          || *state == svn_wc_notify_state_merged
+          || *state == svn_wc_notify_state_changed)
+        {
+          SVN_ERR(record_update_update(merge_b, local_abspath, svn_node_file,
+                                       svn_wc_notify_state_inapplicable,
+                                       *state, scratch_pool));
+        }
+    }
+  else
+    *state = svn_wc_notify_state_unchanged;
+
+  return SVN_NO_ERROR;
+}
+
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+merge_dir_added(svn_wc_notify_state_t *state,
+                svn_boolean_t *tree_conflicted,
+                svn_boolean_t *skip,
+                svn_boolean_t *skip_children,
+                const char *local_relpath,
+                svn_revnum_t rev,
+                const char *copyfrom_path,
+                svn_revnum_t copyfrom_revision,
+                void *baton,
+                apr_pool_t *scratch_pool)
+{
+  merge_cmd_baton_t *merge_b = baton;
+  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;
+  svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
+  svn_boolean_t is_deleted;
+  svn_boolean_t add_existing = FALSE;
+
+  /* Easy out: We are only applying mergeinfo differences. */
+  if (merge_b->record_only)
+    {
+      *state = svn_wc_notify_state_unchanged;
+      return SVN_NO_ERROR;
+    }
+
+
+  /* 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;
+
+      SVN_ERR(check_repos_match(merge_b->target, parent_abspath, copyfrom_url,
+                                scratch_pool));
+    }
+
+  /* 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,
+                                      merge_b, local_abspath, svn_node_unknown,
+                                      scratch_pool));
+
+    /* 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
+     * risk of destroying data. Only skip if it is versioned but unexpectedly
+     * missing from disk, or is unversioned but obstructed by a node of the
+     * wrong kind. */
     if (obstr_state == svn_wc_notify_state_obstructed
         && (is_deleted || kind == svn_node_none))
       {
@@ -2800,145 +2940,6 @@ merge_dir_deleted(svn_wc_notify_state_t 
 
 /* An svn_wc_diff_callbacks4_t function. */
 static svn_error_t *
-merge_dir_opened(svn_boolean_t *tree_conflicted,
-                 svn_boolean_t *skip,
-                 svn_boolean_t *skip_children,
-                 const char *local_relpath,
-                 svn_revnum_t rev,
-                 void *baton,
-                 apr_pool_t *scratch_pool)
-{
-  merge_cmd_baton_t *merge_b = baton;
-  const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
-                                              local_relpath, scratch_pool);
-  svn_node_kind_t wc_kind;
-  svn_wc_notify_state_t obstr_state;
-  svn_boolean_t is_deleted;
-
-  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, &is_deleted, &wc_kind,
-                                    merge_b, local_abspath, svn_node_unknown,
-                                    scratch_pool));
-
-  if (obstr_state != svn_wc_notify_state_inapplicable)
-    {
-      /* In Subversion <= 1.7 we always skipped descendants here */
-      if (obstr_state == svn_wc_notify_state_obstructed)
-        {
-          svn_boolean_t is_wcroot;
-
-          SVN_ERR(svn_wc_check_root(&is_wcroot, NULL, NULL,
-                                  merge_b->ctx->wc_ctx,
-                                  local_abspath, scratch_pool));
-
-          if (is_wcroot)
-            {
-              store_path(merge_b->skipped_abspaths, local_abspath);
-
-              *skip = TRUE;
-              *skip_children = TRUE;
-
-              if (merge_b->ctx->notify_func2)
-                {
-                  svn_wc_notify_t *notify;
-
-                  notify = svn_wc_create_notify(
-                                        local_abspath,
-                                        svn_wc_notify_update_skip_obstruction,
-                                        scratch_pool);
-                  notify->kind = svn_node_dir;
-                  notify->content_state = obstr_state;
-                  merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2,
-                                             notify, scratch_pool);
-                }
-            }
-        }
-
-      return SVN_NO_ERROR;
-    }
-
-  if (wc_kind != svn_node_dir || is_deleted)
-    {
-      if (wc_kind == svn_node_none)
-        {
-          svn_depth_t parent_depth;
-
-          /* If the parent is too shallow to contain this directory,
-           * and the directory is not present on disk, skip it.
-           * Non-inheritable mergeinfo will be recorded, allowing
-           * future merges into non-shallow working copies to merge
-           * changes we missed this time around. */
-          SVN_ERR(svn_wc__node_get_depth(&parent_depth, merge_b->ctx->wc_ctx,
-                                         svn_dirent_dirname(local_abspath,
-                                                            scratch_pool),
-                                         scratch_pool));
-          if (parent_depth != svn_depth_unknown &&
-              parent_depth < svn_depth_immediates)
-            {
-              /* In Subversion <= 1.7 we skipped descendants here */
-              return SVN_NO_ERROR;
-            }
-        }
-
-      /* Check for tree conflicts, if any. */
-
-      /* If we're trying to open a file, the reason for the conflict is
-       * 'replaced'. Because the merge is trying to open the directory,
-       * rather than adding it, the directory must have existed in the
-       * history of the target branch and has been replaced with a file. */
-      if (wc_kind == svn_node_file)
-        {
-          SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_dir,
-                                svn_wc_conflict_action_edit,
-                                svn_wc_conflict_reason_replaced));
-          *tree_conflicted = TRUE;
-          *skip_children = TRUE;
-          SVN_ERR(record_tree_conflict(merge_b, local_abspath, svn_node_dir,
-                                       scratch_pool));
-        }
-
-      /* If we're trying to open a directory that's locally deleted,
-       * or not present because it was deleted in the history of the
-       * target branch, the reason for the conflict is 'deleted'.
-       *
-       * If the DB says something should be here, but there is
-       * nothing on disk, we're probably in a mixed-revision
-       * working copy and the parent has an outdated idea about
-       * the state of its child. Flag a tree conflict in this case
-       * forcing the user to sanity-check the merge result. */
-      else if (is_deleted || wc_kind == svn_node_none)
-        {
-          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));
-              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));
-          *tree_conflicted = TRUE;
-          *skip = TRUE;
-          *skip_children = TRUE;
-          SVN_ERR(record_tree_conflict(merge_b, local_abspath, svn_node_dir,
-                                       scratch_pool));
-        }
-    }
-
-  return SVN_NO_ERROR;
-}
-
-/* An svn_wc_diff_callbacks4_t function. */
-static svn_error_t *
 merge_dir_closed(svn_wc_notify_state_t *contentstate,
                  svn_wc_notify_state_t *propstate,
                  svn_boolean_t *tree_conflicted,