In the conflict resolver, start adding support for merging local edits in
the working copy into an incoming moved directory.

This code is still very rough. It lacks docstrings. Many things won't work yet.

But it already makes 'svn update' apply local file edits to an incoming move,
and makes an existing regression test XPASS, so I'm happy enough to put the
current state of things in to keep working on it incrementally.

* subversion/include/private/svn_wc_private.h
  (svn_wc__conflict_tree_merge_local_changes): Declare.

* subversion/libsvn_client/conflicts.c
  (resolve_incoming_move_dir_merge): Merge local changes with support from
   new functionality added to libsvn_wc in this commit.

* subversion/libsvn_wc/conflicts.c
  (svn_wc__conflict_tree_merge_local_changes): New. This function merges
   local changes (i.e. the delta between BASE and WORKING) from one
   directory in the working copy to another.

* subversion/libsvn_wc/wc_db.h
  (svn_wc__db_merge_local_changes): Declare.

* subversion/libsvn_wc/wc_db_update_move.c
  (tc_editor_merge_local_file_change): New TC editor receiver function.
  (get_working_info): New helper function. Similar to get_infp() but returns
   information about the WORKING tree.
  (walk_local_changes): Recursive walker which drives the TC editor per node.
  (merge_local_changes, svn_wc__db_merge_local_changes): New TC editor driver.
   Has not been tested yet beyond merging of simple file edits.

* subversion/tests/libsvn_client/conflicts-test.c
  (test_funcs): Remove XFAIL marker from test_merge_incoming_move_dir2.


git-svn-id: https://svn.apache.org/repos/asf/subversion/trunk@1754441 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/subversion/include/private/svn_wc_private.h b/subversion/include/private/svn_wc_private.h
index dec877d..58c000f 100644
--- a/subversion/include/private/svn_wc_private.h
+++ b/subversion/include/private/svn_wc_private.h
@@ -1890,6 +1890,24 @@
                                              void *notify_baton,
                                              apr_pool_t *scratch_pool);
 
+/* Merge local changes from a tree conflict victim of an incoming deletion
+ * to the specified DEST_ABSPATH. Both LOCAL_ABSPATH and DEST_ABSPATH must
+ * be directories.
+ *
+ * Assuming DEST_ABSPATH is the correct destination, this function allows
+ * local changes to "follow" incoming moves.
+ *
+ * @since New in 1.10. */
+svn_error_t *
+svn_wc__conflict_tree_merge_local_changes(svn_wc_context_t *wc_ctx,
+                                          const char *local_abspath,
+                                          const char *dest_abspath,
+                                          svn_cancel_func_t cancel_func,
+                                          void *cancel_baton,
+                                          svn_wc_notify_func2_t notify_func,
+                                          void *notify_baton,
+                                          apr_pool_t *scratch_pool);
+
 /* Find nodes in the working copy which corresponds to the new location
  * MOVED_TO_REPOS_RELPATH@REV of the tree conflict victim at VICTIM_ABSPATH.
  * The nodes must be of the same node kind as VICTIM_NODE_KIND.
diff --git a/subversion/libsvn_client/conflicts.c b/subversion/libsvn_client/conflicts.c
index bf6e7eb..5d4e132 100644
--- a/subversion/libsvn_client/conflicts.c
+++ b/subversion/libsvn_client/conflicts.c
@@ -6535,48 +6535,48 @@
   yca_opt_rev.kind = svn_opt_revision_number;
   yca_opt_rev.value.number = yca_loc->rev;
 
-
   err = verify_local_state_for_incoming_delete(conflict, option, scratch_pool);
   if (err)
     goto unlock_wc;
 
-  /* ### We should support local mods in the WC at the moved-away location!
-   * ### Ideally, the victim abspath in the WC would be our merge-right
-   * ### location, instead of the victim's URL. Perhaps we should merge local
-   * ### mods in a second pass here? Until this is addressed, error out. */
-  err = svn_wc__has_local_mods(&is_modified, ctx->wc_ctx, local_abspath, TRUE,
-                               ctx->cancel_func, ctx->cancel_baton,
+  if (operation == svn_wc_operation_merge)
+    {
+      /* Merge YCA_URL->VICTIM_URL into the incoming move target directory. */
+      err = svn_client__merge_locked(&conflict_report,
+                                     yca_loc->url, &yca_opt_rev,
+                                     victim_url, &victim_opt_rev,
+                                     moved_to_abspath, svn_depth_infinity,
+                                     FALSE, FALSE, FALSE, FALSE, FALSE,
+                                     TRUE, /* Allow mixed-rev just in case,
+                                            * since conflict victims can't be
+                                            * updated to straighten out
+                                            * mixed-rev trees. */
+                                     NULL, ctx, scratch_pool, scratch_pool);
+      if (err)
+        goto unlock_wc;
+    }
+
+  /* Merge local modifications into the incoming move target dir. */
+  err = svn_wc__has_local_mods(&is_modified, ctx->wc_ctx, local_abspath,
+                               TRUE, ctx->cancel_func, ctx->cancel_baton,
                                scratch_pool);
   if (err)
     goto unlock_wc;
+
   if (is_modified)
     {
-      err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                              _("Cannot resolve tree conflict on '%s' by "
-                                "following the incoming move and merging "
-                                "('%s' contains local modifications, and "
-                                "merging local modifications to the incoming "
-                                "move target path is not yet supported)"),
-                              svn_dirent_local_style(local_abspath,
-                                                     scratch_pool),
-                              svn_dirent_local_style(local_abspath,
-                                                     scratch_pool));
-      goto unlock_wc;
+      err = svn_wc__conflict_tree_merge_local_changes(ctx->wc_ctx,
+                                                      local_abspath,
+                                                      moved_to_abspath,
+                                                      ctx->cancel_func,
+                                                      ctx->cancel_baton,
+                                                      ctx->notify_func2,
+                                                      ctx->notify_baton2,
+                                                      scratch_pool);
+      if (err)
+        goto unlock_wc;
     }
 
-  /* Merge YCA_URL->VICTIM_URL into the incoming move target directory. */
-  err = svn_client__merge_locked(&conflict_report,
-                                 yca_loc->url, &yca_opt_rev,
-                                 victim_url, &victim_opt_rev,
-                                 moved_to_abspath, svn_depth_infinity,
-                                 FALSE, FALSE, FALSE, FALSE, FALSE,
-                                 TRUE, /* Allow mixed-rev just in case, since
-                                        * conflict victims can't be updated to
-                                        * straighten out mixed-rev trees. */
-                                 NULL, ctx, scratch_pool, scratch_pool);
-  if (err)
-    goto unlock_wc;
-
   if (operation == svn_wc_operation_update ||
       operation == svn_wc_operation_switch)
     {
diff --git a/subversion/libsvn_wc/conflicts.c b/subversion/libsvn_wc/conflicts.c
index c29387e..1887585 100644
--- a/subversion/libsvn_wc/conflicts.c
+++ b/subversion/libsvn_wc/conflicts.c
@@ -3696,6 +3696,77 @@
 }
 
 svn_error_t *
+svn_wc__conflict_tree_merge_local_changes(svn_wc_context_t *wc_ctx,
+                                          const char *local_abspath,
+                                          const char *dest_abspath,
+                                          svn_cancel_func_t cancel_func,
+                                          void *cancel_baton,
+                                          svn_wc_notify_func2_t notify_func,
+                                          void *notify_baton,
+                                          apr_pool_t *scratch_pool)
+{
+  svn_wc_conflict_reason_t local_change;
+  svn_wc_conflict_action_t incoming_change;
+  svn_wc_operation_t operation;
+  svn_boolean_t tree_conflicted;
+  const apr_array_header_t *conflicts;
+  svn_skel_t *conflict_skel;
+
+  SVN_ERR(svn_wc__read_conflicts(&conflicts, &conflict_skel,
+                                 wc_ctx->db, local_abspath,
+                                 FALSE, /* no tempfiles */
+                                 FALSE, /* only tree conflicts */
+                                 scratch_pool, scratch_pool));
+
+  SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, NULL,
+                                     &tree_conflicted, wc_ctx->db,
+                                     local_abspath, conflict_skel,
+                                     scratch_pool, scratch_pool));
+  if (!tree_conflicted)
+    return SVN_NO_ERROR;
+
+  SVN_ERR(svn_wc__conflict_read_tree_conflict(&local_change, &incoming_change,
+                                              NULL, wc_ctx->db, local_abspath,
+                                              conflict_skel,
+                                              scratch_pool, scratch_pool));
+
+  /* Make sure the expected conflict is recorded. */
+  if (operation != svn_wc_operation_update &&
+      operation != svn_wc_operation_switch &&
+      operation != svn_wc_operation_merge)
+    return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                             _("Unexpected conflict operation '%s' on '%s'"),
+                             svn_token__to_word(operation_map, operation),
+                             svn_dirent_local_style(local_abspath,
+                                                    scratch_pool));
+  if (local_change != svn_wc_conflict_reason_edited)
+    return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                             _("Unexpected conflict reason '%s' on '%s'"),
+                             svn_token__to_word(reason_map, local_change),
+                             svn_dirent_local_style(local_abspath,
+                                                    scratch_pool));
+  if (incoming_change != svn_wc_conflict_action_delete)
+    return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                             _("Unexpected conflict action '%s' on '%s'"),
+                             svn_token__to_word(action_map, incoming_change),
+                             svn_dirent_local_style(local_abspath,
+                                                    scratch_pool));
+
+  /* Merge local changes. */
+  SVN_ERR(svn_wc__db_merge_local_changes(wc_ctx->db, local_abspath,
+                                         dest_abspath, operation,
+                                         incoming_change, local_change,
+                                         cancel_func, cancel_baton,
+                                         notify_func, notify_baton,
+                                         scratch_pool));
+
+  SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath, cancel_func, cancel_baton,
+                         scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
 svn_wc__guess_incoming_move_target_nodes(apr_array_header_t **possible_targets,
                                          svn_wc_context_t *wc_ctx,
                                          const char *victim_abspath,
diff --git a/subversion/libsvn_wc/wc_db.h b/subversion/libsvn_wc/wc_db.h
index 36ebce2..e60469c 100644
--- a/subversion/libsvn_wc/wc_db.h
+++ b/subversion/libsvn_wc/wc_db.h
@@ -3404,6 +3404,22 @@
                                              void *notify_baton,
                                              apr_pool_t *scratch_pool);
 
+/* Merge local changes from tree conflict victim at LOCAL_ABSPATH into the
+   directory at DEST_ABSPATH. This function requires that LOCAL_ABSPATH is
+   a directory and a tree-conflict victim. DST_ABSPATH must be a directory. */
+svn_error_t *
+svn_wc__db_merge_local_changes(svn_wc__db_t *db,
+                               const char *local_abspath,
+                               const char *dest_abspath,
+                               svn_wc_operation_t operation,
+                               svn_wc_conflict_action_t action,
+                               svn_wc_conflict_reason_t reason,
+                               svn_cancel_func_t cancel_func,
+                               void *cancel_baton,
+                               svn_wc_notify_func2_t notify_func,
+                               void *notify_baton,
+                               apr_pool_t *scratch_pool);
+
 /* LOCAL_ABSPATH is moved to MOVE_DST_ABSPATH.  MOVE_SRC_ROOT_ABSPATH
  * is the root of the move to MOVE_DST_OP_ROOT_ABSPATH.
  * DELETE_ABSPATH is the op-root of the move; it's the same
diff --git a/subversion/libsvn_wc/wc_db_update_move.c b/subversion/libsvn_wc/wc_db_update_move.c
index efccb95..6ad2f59 100644
--- a/subversion/libsvn_wc/wc_db_update_move.c
+++ b/subversion/libsvn_wc/wc_db_update_move.c
@@ -1197,6 +1197,135 @@
 }
 
 static svn_error_t *
+tc_editor_merge_local_file_change(node_move_baton_t *nmb,
+                                  const char *dst_relpath,
+                                  const char *src_relpath,
+                                  const svn_checksum_t *src_checksum,
+                                  const svn_checksum_t *dst_checksum,
+                                  apr_hash_t *dst_props,
+                                  apr_hash_t *src_props,
+                                  svn_boolean_t do_text_merge,
+                                  apr_pool_t *scratch_pool)
+{
+  update_move_baton_t *b = nmb->umb;
+  working_node_version_t old_version, new_version;
+  const char *dst_abspath = svn_dirent_join(b->wcroot->abspath,
+                                            dst_relpath,
+                                            scratch_pool);
+  svn_skel_t *conflict_skel = NULL;
+  apr_hash_t *actual_props;
+  apr_array_header_t *propchanges;
+  enum svn_wc_merge_outcome_t merge_outcome;
+  svn_wc_notify_state_t prop_state, content_state;
+  svn_skel_t *work_item, *work_items = NULL;
+  svn_node_kind_t dst_kind_on_disk;
+  svn_boolean_t obstructed = FALSE;
+
+  SVN_ERR(mark_node_edited(nmb, scratch_pool));
+  if (nmb->skip)
+    return SVN_NO_ERROR;
+
+  SVN_ERR(svn_io_check_path(dst_abspath, &dst_kind_on_disk, scratch_pool));
+  if (dst_kind_on_disk != svn_node_none && dst_kind_on_disk != svn_node_file)
+    {
+      SVN_ERR(create_node_tree_conflict(&conflict_skel, nmb, dst_relpath,
+                                        svn_node_file, svn_node_file,
+                                        svn_wc_conflict_reason_obstructed,
+                                        svn_wc_conflict_action_edit,
+                                        NULL,
+                                        scratch_pool, scratch_pool));
+      obstructed = TRUE;
+    }
+
+  old_version.location_and_kind = b->old_version;
+  new_version.location_and_kind = b->new_version;
+
+  old_version.checksum = src_checksum;
+  old_version.props = src_props;
+  new_version.checksum = dst_checksum;
+  new_version.props = dst_props;
+
+  SVN_ERR(update_working_props(&prop_state, &conflict_skel, &propchanges,
+                               &actual_props, b, dst_relpath,
+                               &old_version, &new_version,
+                               scratch_pool, scratch_pool));
+
+  if (!obstructed && do_text_merge)
+    {
+      const char *old_pristine_abspath;
+      const char *src_abspath;
+
+      /*
+       * Run a 3-way merge to update the file at its post-move location, using
+       * the pre-move file's pristine text as the merge base, the post-move
+       * content as the merge-left version, and the current content of the
+       * working file at the pre-move location as the merge-right version.
+       */
+      SVN_ERR(svn_wc__db_pristine_get_path(&old_pristine_abspath,
+                                           b->db, b->wcroot->abspath,
+                                           src_checksum,
+                                           scratch_pool, scratch_pool));
+      src_abspath = svn_dirent_join(b->wcroot->abspath, src_relpath,
+                                    scratch_pool);
+      SVN_ERR(svn_wc__internal_merge(&work_item, &conflict_skel,
+                                     &merge_outcome, b->db,
+                                     old_pristine_abspath,
+                                     src_abspath,
+                                     dst_abspath,
+                                     dst_abspath,
+                                     NULL, NULL, NULL, /* diff labels */
+                                     actual_props,
+                                     FALSE, /* dry-run */
+                                     NULL, /* diff3-cmd */
+                                     NULL, /* merge options */
+                                     propchanges,
+                                     b->cancel_func, b->cancel_baton,
+                                     scratch_pool, scratch_pool));
+
+      work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+
+      if (merge_outcome == svn_wc_merge_conflict)
+        content_state = svn_wc_notify_state_conflicted;
+      else
+        content_state = svn_wc_notify_state_merged;
+    }
+  else
+    content_state = svn_wc_notify_state_unchanged;
+
+  /* If there are any conflicts to be stored, convert them into work items
+   * too. */
+  if (conflict_skel)
+    {
+      const char *dst_repos_relpath;
+
+      SVN_ERR(svn_wc__db_depth_get_info(NULL, NULL, NULL,
+                                        &dst_repos_relpath, NULL, NULL,
+                                        NULL, NULL, NULL, NULL, NULL, NULL,
+                                        NULL,
+                                        b->wcroot, dst_relpath,
+                                        b->dst_op_depth,
+                                        scratch_pool, scratch_pool));
+
+      SVN_ERR(create_conflict_markers(&work_item, dst_abspath, b->db,
+                                      dst_repos_relpath, conflict_skel,
+                                      b->operation, &old_version, &new_version,
+                                      svn_node_file, !obstructed,
+                                      scratch_pool, scratch_pool));
+
+      work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+    }
+
+  SVN_ERR(update_move_list_add(b->wcroot, dst_relpath, b->db,
+                               svn_wc_notify_update_update,
+                               svn_node_file,
+                               content_state,
+                               prop_state,
+                               conflict_skel, work_items, scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
 tc_editor_delete(node_move_baton_t *nmb,
                  const char *relpath,
                  svn_node_kind_t old_kind,
@@ -1827,6 +1956,402 @@
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+get_working_info(apr_hash_t **props,
+                 const svn_checksum_t **checksum,
+                 apr_array_header_t **children,
+                 svn_node_kind_t *kind,
+                 const char *local_relpath,
+                 svn_wc__db_wcroot_t *wcroot,
+                 apr_pool_t *result_pool,
+                 apr_pool_t *scratch_pool)
+{
+  svn_wc__db_status_t status;
+  const char *repos_relpath;
+  svn_node_kind_t db_kind;
+  svn_error_t *err;
+
+  err = svn_wc__db_read_info_internal(&status, &db_kind, NULL, &repos_relpath,
+                                      NULL, NULL, NULL, NULL, NULL,
+                                      checksum,
+                                      NULL, NULL, NULL, NULL, NULL,
+                                      NULL, NULL, NULL, NULL, NULL,
+                                      NULL, NULL, NULL, NULL, NULL,
+                                      wcroot, local_relpath,
+                                      result_pool, scratch_pool);
+
+  /* If there is no node, or only a node that describes a delete
+     of a lower layer we report this node as not existing. */
+  if ((err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+      || (!err && status != svn_wc__db_status_added
+               && status != svn_wc__db_status_normal))
+    {
+      svn_error_clear(err);
+
+      if (kind)
+        *kind = svn_node_none;
+      if (checksum)
+        *checksum = NULL;
+      if (props)
+        *props = NULL;
+      if (children)
+        *children = apr_array_make(result_pool, 0, sizeof(const char *));
+
+      return SVN_NO_ERROR;
+    }
+  else
+    SVN_ERR(err);
+
+  SVN_ERR(svn_wc__db_read_props_internal(props, wcroot, local_relpath,
+                                         result_pool, scratch_pool));
+
+  if (kind)
+    *kind = db_kind;
+
+  if (children && db_kind == svn_node_dir)
+    {
+      svn_sqlite__stmt_t *stmt;
+      svn_boolean_t have_row;
+
+      *children = apr_array_make(result_pool, 16, sizeof(const char *));
+      SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+                                        STMT_SELECT_WORKING_CHILDREN));
+      SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+      SVN_ERR(svn_sqlite__step(&have_row, stmt));
+      while (have_row)
+        {
+          const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+
+          APR_ARRAY_PUSH(*children, const char *)
+              = svn_relpath_basename(child_relpath, result_pool);
+
+          SVN_ERR(svn_sqlite__step(&have_row, stmt));
+        }
+      SVN_ERR(svn_sqlite__reset(stmt));
+    }
+  else if (children)
+    *children = apr_array_make(result_pool, 0, sizeof(const char *));
+
+  return SVN_NO_ERROR;
+}
+
+/* ### Drive TC_EDITOR so as to ...
+ */
+static svn_error_t *
+walk_local_changes(node_move_baton_t *nmb,
+                   svn_wc__db_wcroot_t *wcroot,
+                   const char *src_relpath,
+                   const char *dst_relpath,
+                   apr_pool_t *scratch_pool)
+{
+  update_move_baton_t *b = nmb->umb;
+  svn_node_kind_t src_kind, dst_kind;
+  const svn_checksum_t *src_checksum, *dst_checksum;
+  apr_hash_t *src_props, *dst_props;
+  apr_array_header_t *src_children, *dst_children;
+
+  if (b->cancel_func)
+    SVN_ERR(b->cancel_func(b->cancel_baton));
+
+  SVN_ERR(get_working_info(&src_props, &src_checksum, &src_children,
+                           &src_kind, src_relpath, wcroot, scratch_pool,
+                           scratch_pool));
+
+  SVN_ERR(get_info(&dst_props, &dst_checksum, &dst_children, &dst_kind,
+                   dst_relpath, b->dst_op_depth,
+                   wcroot, scratch_pool, scratch_pool));
+
+  if (src_kind == svn_node_none
+      || (dst_kind != svn_node_none && src_kind != dst_kind))
+    {
+      SVN_ERR(tc_editor_delete(nmb, dst_relpath, dst_kind, src_kind,
+                               scratch_pool));
+    }
+
+  if (nmb->skip)
+    return SVN_NO_ERROR;
+
+  if (src_kind != svn_node_none && src_kind != dst_kind)
+    {
+      if (src_kind == svn_node_file || src_kind == svn_node_symlink)
+        {
+          SVN_ERR(tc_editor_add_file(nmb, dst_relpath, dst_kind,
+                                     src_checksum, src_props,
+                                     scratch_pool));
+        }
+      else if (src_kind == svn_node_dir)
+        {
+          SVN_ERR(tc_editor_add_directory(nmb, dst_relpath, dst_kind,
+                                          src_props, scratch_pool));
+        }
+    }
+  else if (src_kind != svn_node_none)
+    {
+      svn_boolean_t props_equal;
+
+      SVN_ERR(props_match(&props_equal, src_props, dst_props, scratch_pool));
+
+      if (src_kind == svn_node_file || src_kind == svn_node_symlink)
+        {
+          svn_boolean_t is_modified;
+
+          SVN_ERR(svn_wc__internal_file_modified_p(&is_modified, b->db,
+                                                   svn_dirent_join(
+                                                     b->wcroot->abspath,
+                                                     src_relpath,
+                                                     scratch_pool),
+                                                   FALSE /* exact_comparison */,
+                                                   scratch_pool));
+          if (!props_equal || is_modified)
+            SVN_ERR(tc_editor_merge_local_file_change(nmb, dst_relpath,
+                                                      src_relpath,
+                                                      src_checksum,
+                                                      dst_checksum,
+                                                      dst_props, src_props,
+                                                      is_modified,
+                                                      scratch_pool));
+        }
+      else if (src_kind == svn_node_dir)
+        {
+          if (!props_equal)
+            SVN_ERR(tc_editor_alter_directory(nmb, dst_relpath,
+                                              dst_props, src_props,
+                                              scratch_pool));
+        }
+    }
+
+  if (nmb->skip)
+    return SVN_NO_ERROR;
+
+  if (src_kind == svn_node_dir)
+    {
+      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+      int i = 0, j = 0;
+
+      while (i < src_children->nelts || j < dst_children->nelts)
+        {
+          const char *child_name;
+          svn_boolean_t src_only = FALSE, dst_only = FALSE;
+          node_move_baton_t cnmb = { 0 };
+
+          cnmb.pb = nmb;
+          cnmb.umb = nmb->umb;
+          cnmb.shadowed = nmb->shadowed;
+
+          svn_pool_clear(iterpool);
+          if (i >= src_children->nelts)
+            {
+              dst_only = TRUE;
+              child_name = APR_ARRAY_IDX(dst_children, j, const char *);
+            }
+          else if (j >= dst_children->nelts)
+            {
+              src_only = TRUE;
+              child_name = APR_ARRAY_IDX(src_children, i, const char *);
+            }
+          else
+            {
+              const char *src_name = APR_ARRAY_IDX(src_children, i,
+                                                   const char *);
+              const char *dst_name = APR_ARRAY_IDX(dst_children, j,
+                                                   const char *);
+              int cmp = strcmp(src_name, dst_name);
+
+              if (cmp > 0)
+                dst_only = TRUE;
+              else if (cmp < 0)
+                src_only = TRUE;
+
+              child_name = dst_only ? dst_name : src_name;
+            }
+
+          cnmb.src_relpath = svn_relpath_join(src_relpath, child_name,
+                                              iterpool);
+          cnmb.dst_relpath = svn_relpath_join(dst_relpath, child_name,
+                                              iterpool);
+
+          if (!cnmb.shadowed)
+            SVN_ERR(check_node_shadowed(&cnmb.shadowed, wcroot,
+                                        cnmb.dst_relpath, b->dst_op_depth,
+                                        iterpool));
+
+          SVN_ERR(walk_local_changes(&cnmb, wcroot, cnmb.src_relpath,
+                                     cnmb.dst_relpath, iterpool));
+
+          if (!dst_only)
+            ++i;
+          if (!src_only)
+            ++j;
+
+          if (nmb->skip) /* Does parent now want a skip? */
+            break;
+        }
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* The body of svn_wc__db_merge_local_changes(). */
+static svn_error_t *
+merge_local_changes(svn_revnum_t *old_rev,
+                    svn_revnum_t *new_rev,
+                    svn_wc__db_t *db,
+                    svn_wc__db_wcroot_t *wcroot,
+                    const char *local_relpath,
+                    const char *dst_relpath,
+                    svn_wc_operation_t operation,
+                    svn_wc_conflict_action_t action,
+                    svn_wc_conflict_reason_t reason,
+                    svn_cancel_func_t cancel_func,
+                    void *cancel_baton,
+                    apr_pool_t *scratch_pool)
+{
+  update_move_baton_t umb = { NULL };
+  svn_wc_conflict_version_t old_version;
+  svn_wc_conflict_version_t new_version;
+  apr_int64_t repos_id;
+  node_move_baton_t nmb = { 0 };
+
+  SVN_ERR_ASSERT(svn_relpath_skip_ancestor(dst_relpath, local_relpath) == NULL);
+
+  /* In case of 'merge' the source is in the BASE tree (+ local mods) and the
+   * destination is a copied tree. For update/switch the source is a copied
+   * tree (copied from the pre-update BASE revision when the tree conflict
+   * was raised), and the destination is in the BASE tree. */
+  if (operation == svn_wc_operation_merge)
+    {
+      umb.src_op_depth = 0;
+      umb.dst_op_depth = relpath_depth(dst_relpath);
+    }
+  else
+    {
+      umb.src_op_depth = relpath_depth(local_relpath);
+      umb.dst_op_depth = 0;
+    }
+
+  SVN_ERR(verify_write_lock(wcroot, local_relpath, scratch_pool));
+  SVN_ERR(verify_write_lock(wcroot, dst_relpath, scratch_pool));
+
+  SVN_ERR(svn_wc__db_depth_get_info(NULL, &new_version.node_kind,
+                                    &new_version.peg_rev,
+                                    &new_version.path_in_repos, &repos_id,
+                                    NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+                                    NULL,
+                                    wcroot, local_relpath, umb.src_op_depth,
+                                    scratch_pool, scratch_pool));
+
+  SVN_ERR(svn_wc__db_fetch_repos_info(&new_version.repos_url,
+                                      &new_version.repos_uuid,
+                                      wcroot, repos_id,
+                                      scratch_pool));
+
+  SVN_ERR(svn_wc__db_depth_get_info(NULL, &old_version.node_kind,
+                                    &old_version.peg_rev,
+                                    &old_version.path_in_repos, &repos_id,
+                                    NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+                                    NULL,
+                                    wcroot, dst_relpath, umb.dst_op_depth,
+                                    scratch_pool, scratch_pool));
+
+  SVN_ERR(svn_wc__db_fetch_repos_info(&old_version.repos_url,
+                                      &old_version.repos_uuid,
+                                      wcroot, repos_id,
+                                      scratch_pool));
+  *old_rev = old_version.peg_rev;
+  *new_rev = new_version.peg_rev;
+
+  umb.operation = operation;
+  umb.old_version= &old_version;
+  umb.new_version= &new_version;
+  umb.db = db;
+  umb.wcroot = wcroot;
+  umb.cancel_func = cancel_func;
+  umb.cancel_baton = cancel_baton;
+
+  if (umb.src_op_depth == 0)
+    SVN_ERR(suitable_for_move(wcroot, local_relpath, scratch_pool));
+
+  /* Create a new, and empty, list for notification information. */
+  SVN_ERR(svn_sqlite__exec_statements(wcroot->sdb,
+                                      STMT_CREATE_UPDATE_MOVE_LIST));
+
+  /* Drive the editor... */
+
+  nmb.umb = &umb;
+  nmb.src_relpath = local_relpath;
+  nmb.dst_relpath = dst_relpath;
+  /* nmb.shadowed = FALSE; */
+  /* nmb.edited = FALSE; */
+  /* nmb.skip_children = FALSE; */
+
+  /* We walk the move source, comparing each node with the equivalent node at
+   * the move destination and applying any local changes to nodes at the move
+   destination. */
+  SVN_ERR(walk_local_changes(&nmb, wcroot, local_relpath, dst_relpath,
+                             scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_merge_local_changes(svn_wc__db_t *db,
+                               const char *local_abspath,
+                               const char *dest_abspath,
+                               svn_wc_operation_t operation,
+                               svn_wc_conflict_action_t action,
+                               svn_wc_conflict_reason_t reason,
+                               svn_cancel_func_t cancel_func,
+                               void *cancel_baton,
+                               svn_wc_notify_func2_t notify_func,
+                               void *notify_baton,
+                               apr_pool_t *scratch_pool)
+{
+  svn_wc__db_wcroot_t *wcroot;
+  svn_revnum_t old_rev, new_rev;
+  const char *local_relpath;
+  const char *dest_relpath;
+
+  /* ### Check for mixed-rev src or dst? */
+
+  SVN_ERR(svn_wc__db_wcroot_parse_local_abspath(&wcroot, &local_relpath,
+                                                db, local_abspath,
+                                                scratch_pool, scratch_pool));
+  VERIFY_USABLE_WCROOT(wcroot);
+
+  dest_relpath
+    = svn_dirent_skip_ancestor(wcroot->abspath, dest_abspath);
+
+  SVN_WC__DB_WITH_TXN(merge_local_changes(&old_rev, &new_rev, db, wcroot,
+                                          local_relpath, dest_relpath,
+                                          operation, action, reason,
+                                          cancel_func, cancel_baton,
+                                          scratch_pool),
+                      wcroot);
+
+  /* Send all queued up notifications. */
+  SVN_ERR(svn_wc__db_update_move_list_notify(wcroot, old_rev, new_rev,
+                                             notify_func, notify_baton,
+                                             scratch_pool));
+  if (notify_func)
+    {
+      svn_wc_notify_t *notify;
+
+      notify = svn_wc_create_notify(svn_dirent_join(wcroot->abspath,
+                                                    local_relpath,
+                                                    scratch_pool),
+                                    svn_wc_notify_update_completed,
+                                    scratch_pool);
+      notify->kind = svn_node_none;
+      notify->content_state = svn_wc_notify_state_inapplicable;
+      notify->prop_state = svn_wc_notify_state_inapplicable;
+      notify->revision = new_rev;
+      notify_func(notify_baton, notify, scratch_pool);
+    }
+
+
+  return SVN_NO_ERROR;
+}
+
 /* Set *CAN_BUMP to TRUE if DEPTH is sufficient to cover the entire
    tree  LOCAL_RELPATH at OP_DEPTH, to FALSE otherwise. */
 static svn_error_t *
diff --git a/subversion/tests/libsvn_client/conflicts-test.c b/subversion/tests/libsvn_client/conflicts-test.c
index 9c05eb9..d85dedc 100644
--- a/subversion/tests/libsvn_client/conflicts-test.c
+++ b/subversion/tests/libsvn_client/conflicts-test.c
@@ -2408,8 +2408,8 @@
     SVN_TEST_OPTS_PASS(test_switch_incoming_move_file_text_merge,
                        "switch incoming move file text merge"),
     SVN_TEST_OPTS_PASS(test_merge_incoming_move_dir, "merge incoming move dir"),
-    SVN_TEST_OPTS_XFAIL(test_merge_incoming_move_dir2,
-                       "merge incoming move dir with local mods"),
+    SVN_TEST_OPTS_PASS(test_merge_incoming_move_dir2,
+                       "merge incoming move dir with local mod"),
     SVN_TEST_OPTS_PASS(test_merge_incoming_delete_vs_local_delete,
                        "merge incoming delete vs local delete"),
     SVN_TEST_NULL