You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by cm...@apache.org on 2018/11/28 21:25:35 UTC

svn commit: r1847678 [5/25] - in /subversion/branches/swig-py3: ./ build/ build/ac-macros/ build/generator/ build/generator/templates/ build/win32/ contrib/client-side/ contrib/client-side/svn_load_dirs/ contrib/client-side/svnmerge/ contrib/hook-scrip...

Modified: subversion/branches/swig-py3/subversion/libsvn_client/conflicts.c
URL: http://svn.apache.org/viewvc/subversion/branches/swig-py3/subversion/libsvn_client/conflicts.c?rev=1847678&r1=1847677&r2=1847678&view=diff
==============================================================================
--- subversion/branches/swig-py3/subversion/libsvn_client/conflicts.c (original)
+++ subversion/branches/swig-py3/subversion/libsvn_client/conflicts.c Wed Nov 28 21:25:32 2018
@@ -255,7 +255,7 @@ struct repos_move_info {
   /* The revision in which this move was committed. */
   svn_revnum_t rev;
 
-  /* The author who commited the revision in which this move was committed. */
+  /* The author who committed the revision in which this move was committed. */
   const char *rev_author;
 
   /* The repository relpath the node was moved from in this revision. */
@@ -806,18 +806,20 @@ map_deleted_path_to_move(const char *del
   if (closest_move)
     {
       const char *relpath;
-      const char *moved_along_path;
-      struct repos_move_info *move;
       
       /* See if we can find an even closer move for this moved-along path. */
       relpath = svn_relpath_skip_ancestor(closest_move->moved_to_repos_relpath,
                                           deleted_relpath);
-      moved_along_path =
-        svn_relpath_join(closest_move->moved_from_repos_relpath, relpath,
-                         scratch_pool);
-      move = map_deleted_path_to_move(moved_along_path, moves, scratch_pool);
-      if (move)
-        return move;
+      if (relpath && relpath[0] != '\0')
+        {
+          struct repos_move_info *move;
+          const char *moved_along_path =
+            svn_relpath_join(closest_move->moved_from_repos_relpath, relpath,
+                             scratch_pool);
+          move = map_deleted_path_to_move(moved_along_path, moves, scratch_pool);
+          if (move)
+            return move;
+        }
     }
 
   return closest_move;
@@ -1059,6 +1061,9 @@ find_deleted_rev(void *baton,
     {
       apr_array_header_t *moves;
 
+      if (b->moves_table == NULL)
+        return SVN_NO_ERROR;
+
       moves = apr_hash_get(b->moves_table, &log_entry->revision,
                            sizeof(svn_revnum_t));
       if (moves)
@@ -2097,33 +2102,6 @@ trace_moved_node_backwards(apr_hash_t *m
   return SVN_NO_ERROR;
 }
 
-static svn_error_t *
-reparent_session_and_fetch_node_kind(svn_node_kind_t *node_kind,
-                                     svn_ra_session_t *ra_session,
-                                     const char *url,
-                                     svn_revnum_t peg_rev,
-                                     apr_pool_t *scratch_pool)
-{
-  svn_error_t *err;
-
-  err = svn_ra_reparent(ra_session, url, scratch_pool);
-  if (err)
-    {
-      if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
-        {
-          svn_error_clear(err);
-          *node_kind = svn_node_unknown;
-          return SVN_NO_ERROR;
-        }
-    
-      return svn_error_trace(err);
-    }
-
-  SVN_ERR(svn_ra_check_path(ra_session, "", peg_rev, node_kind, scratch_pool));
-
-  return SVN_NO_ERROR;
-}
-
 /* Scan MOVES_TABLE for moves which affect a particular deleted node, and
  * build a set of new move information for this node.
  * Return heads of all possible move chains in *MOVES.
@@ -2170,22 +2148,29 @@ find_operative_moves(apr_array_header_t
       svn_pool_clear(iterpool);
 
       move = APR_ARRAY_IDX(moves_in_deleted_rev, i, struct repos_move_info *);
-      relpath = svn_relpath_skip_ancestor(move->moved_from_repos_relpath,
+      if (strcmp(move->moved_from_repos_relpath, deleted_repos_relpath) == 0)
+        {
+          APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move;
+          continue;
+        }
+
+      /* Test for an operative nested move. */
+      relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
                                           deleted_repos_relpath);
       if (relpath && relpath[0] != '\0')
         {
-          svn_node_kind_t node_kind;
+          struct repos_move_info *nested_move;
+          const char *actual_deleted_repos_relpath;
 
-          url = svn_path_url_add_component2(repos_root_url,
-                                            deleted_repos_relpath,
-                                            iterpool);
-          SVN_ERR(reparent_session_and_fetch_node_kind(&node_kind,
-                                                       ra_session, url,
-                                                       rev_below(deleted_rev),
-                                                       iterpool));
-          move = new_path_adjusted_move(move, relpath, node_kind, result_pool);
+          actual_deleted_repos_relpath =
+              svn_relpath_join(move->moved_from_repos_relpath, relpath,
+                               iterpool);
+          nested_move = map_deleted_path_to_move(actual_deleted_repos_relpath,
+                                                 moves_in_deleted_rev,
+                                                 iterpool);
+          if (nested_move)
+            APR_ARRAY_PUSH(*moves, struct repos_move_info *) = nested_move;
         }
-      APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move;
     }
 
   if (url != NULL)
@@ -2223,8 +2208,8 @@ find_operative_moves(apr_array_header_t
  * If the node was replaced rather than deleted, set *REPLACING_NODE_KIND to
  * the node kind of the replacing node. Else, set it to svn_node_unknown.
  * Only request the log for revisions up to END_REV from the server.
- * If the deleted node was moved, provide heads of move chains in *MOVES.
- * If the node was not moved,set *MOVES to NULL.
+ * If MOVES it not NULL, and the deleted node was moved, provide heads of
+ * move chains in *MOVES, or, if the node was not moved, set *MOVES to NULL.
  */
 static svn_error_t *
 find_revision_for_suspected_deletion(svn_revnum_t *deleted_rev,
@@ -2261,10 +2246,11 @@ find_revision_for_suspected_deletion(svn
                                              scratch_pool));
   victim_abspath = svn_client_conflict_get_local_abspath(conflict);
 
-  SVN_ERR(find_moves_in_revision_range(&moves_table, parent_repos_relpath,
-                                       repos_root_url, repos_uuid,
-                                       victim_abspath, start_rev, end_rev,
-                                       ctx, result_pool, scratch_pool));
+  if (moves)
+    SVN_ERR(find_moves_in_revision_range(&moves_table, parent_repos_relpath,
+                                         repos_root_url, repos_uuid,
+                                         victim_abspath, start_rev, end_rev,
+                                         ctx, result_pool, scratch_pool));
 
   url = svn_path_url_add_component2(repos_root_url, parent_repos_relpath,
                                     scratch_pool);
@@ -2289,7 +2275,8 @@ find_revision_for_suspected_deletion(svn
   b.repos_root_url = repos_root_url;
   b.repos_uuid = repos_uuid;
   b.ctx = ctx;
-  b.moves_table = moves_table;
+  if (moves)
+    b.moves_table = moves_table;
   b.result_pool = result_pool;
   SVN_ERR(svn_ra__dup_session(&b.extra_ra_session, ra_session, NULL,
                               scratch_pool, scratch_pool));
@@ -2319,7 +2306,7 @@ find_revision_for_suspected_deletion(svn
     {
       struct repos_move_info *move = b.move;
 
-      if (move)
+      if (moves && move)
         {
           *deleted_rev = move->rev;
           *deleted_rev_author = move->rev_author;
@@ -2337,7 +2324,8 @@ find_revision_for_suspected_deletion(svn
           *deleted_rev = SVN_INVALID_REVNUM;
           *deleted_rev_author = NULL;
           *replacing_node_kind = svn_node_unknown;
-          *moves = NULL;
+          if (moves)
+            *moves = NULL;
         }
       return SVN_NO_ERROR;
     }
@@ -2346,10 +2334,11 @@ find_revision_for_suspected_deletion(svn
       *deleted_rev = b.deleted_rev;
       *deleted_rev_author = b.deleted_rev_author;
       *replacing_node_kind = b.replacing_node_kind;
-      SVN_ERR(find_operative_moves(moves, moves_table,
-                                   b.deleted_repos_relpath, b.deleted_rev,
-                                   ra_session, repos_root_url,
-                                   result_pool, scratch_pool));
+      if (moves)
+        SVN_ERR(find_operative_moves(moves, moves_table,
+                                     b.deleted_repos_relpath, b.deleted_rev,
+                                     ra_session, repos_root_url,
+                                     result_pool, scratch_pool));
     }
 
   return SVN_NO_ERROR;
@@ -2361,6 +2350,8 @@ struct conflict_tree_local_missing_detai
   /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. */
   svn_revnum_t deleted_rev;
 
+  /* ### Add 'added_rev', like in conflict_tree_incoming_delete_details? */
+
   /* Author who committed DELETED_REV. */
   const char *deleted_rev_author;
 
@@ -2368,21 +2359,49 @@ struct conflict_tree_local_missing_detai
   const char *deleted_repos_relpath;
 
   /* Move information about the conflict victim. If not NULL, this is an
-   * array of repos_move_info elements. Each element is the head of a
-   * move chain which starts in DELETED_REV. */
+   * array of 'struct repos_move_info *' elements. Each element is the
+   * head of a move chain which starts in DELETED_REV. */
   apr_array_header_t *moves;
 
+  /* If moves is not NULL, a map of repos_relpaths and working copy nodes.
+  *
+   * Each key is a "const char *" repository relpath corresponding to a
+   * possible repository-side move destination node in the revision which
+   * is the merge-right revision in case of a merge.
+   *
+   * Each value is an apr_array_header_t *.
+   * Each array consists of "const char *" absolute paths to working copy
+   * nodes which correspond to the repository node selected by the map key.
+   * Each such working copy node is a potential local move target which can
+   * be chosen to find a suitable merge target when resolving a tree conflict.
+   *
+   * This may be an empty hash map in case if there is no move target path
+   * in the working copy. */
+  apr_hash_t *wc_move_targets;
+
+  /* If not NULL, the preferred move target repository relpath. This is our key
+   * into the WC_MOVE_TARGETS map above (can be overridden by the user). */
+  const char *move_target_repos_relpath;
+
+  /* The current index into the list of working copy nodes corresponding to
+   * MOVE_TARGET_REPOS_REPLATH (can be overridden by the user). */
+  int wc_move_target_idx;
+
   /* Move information about siblings. Siblings are nodes which share
    * a youngest common ancestor with the conflict victim. E.g. in case
    * of a merge operation they are part of the merge source branch.
-   * If not NULL, this is an array of repos_move_info elements.
+   * If not NULL, this is an array of 'struct repos_move_info *' elements.
    * Each element is the head of a move chain, which starts at some
    * point in history after siblings and conflict victim forked off
    * their common ancestor. */
   apr_array_header_t *sibling_moves;
 
-  /* If not NULL, this is the move target abspath. */
-  const char *moved_to_abspath;
+  /* List of nodes in the WC which are suitable merge targets for changes
+   * merged from any moved sibling. Array elements are 'const char *'
+   * absolute paths of working copy nodes. This array contains multiple
+   * elements only if ambiguous matches were found in the WC. */
+  apr_array_header_t *wc_siblings;
+  int preferred_sibling_idx;
 };
 
 static svn_error_t *
@@ -2601,6 +2620,168 @@ find_moves_in_natural_history(apr_array_
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+collect_sibling_move_candidates(apr_array_header_t *candidates,
+                                const char *victim_abspath,
+                                svn_node_kind_t victim_kind,
+                                struct repos_move_info *move,
+                                svn_client_ctx_t *ctx,
+                                apr_pool_t *result_pool,
+                                apr_pool_t *scratch_pool)
+{ 
+  const char *basename;
+  apr_array_header_t *abspaths;
+  int i;
+
+  basename = svn_relpath_basename(move->moved_from_repos_relpath, scratch_pool);
+  SVN_ERR(svn_wc__find_working_nodes_with_basename(&abspaths, victim_abspath,
+                                                   basename, victim_kind,
+                                                   ctx->wc_ctx, result_pool,
+                                                   scratch_pool));
+  apr_array_cat(candidates, abspaths);
+
+  if (move->next)
+    {
+      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+      for (i = 0; i < move->next->nelts; i++)
+        {
+          struct repos_move_info *next_move;
+          next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *);
+          SVN_ERR(collect_sibling_move_candidates(candidates, victim_abspath,
+                                                  victim_kind, next_move, ctx,
+                                                  result_pool, iterpool));
+          svn_pool_clear(iterpool);
+        }
+      svn_pool_destroy(iterpool);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Follow each move chain starting a MOVE all the way to the end to find
+ * the possible working copy locations for VICTIM_ABSPATH which corresponds
+ * to VICTIM_REPOS_REPLATH@VICTIM_REVISION.
+ * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the
+ * repos_relpath which is the corresponding move destination in the repository.
+ * This function is recursive. */
+static svn_error_t *
+follow_move_chains(apr_hash_t *wc_move_targets,
+                   struct repos_move_info *move,
+                   svn_client_ctx_t *ctx,
+                   const char *victim_abspath,
+                   svn_node_kind_t victim_node_kind,
+                   const char *victim_repos_relpath,
+                   svn_revnum_t victim_revision,
+                   apr_pool_t *result_pool,
+                   apr_pool_t *scratch_pool)
+{
+  apr_array_header_t *candidate_abspaths;
+
+  /* Gather candidate nodes which represent this moved_to_repos_relpath. */
+  SVN_ERR(svn_wc__guess_incoming_move_target_nodes(
+            &candidate_abspaths, ctx->wc_ctx,
+            victim_abspath, victim_node_kind,
+            move->moved_to_repos_relpath,
+            scratch_pool, scratch_pool));
+
+  if (candidate_abspaths->nelts > 0)
+    {
+      apr_array_header_t *moved_to_abspaths;
+      int i;
+      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+      moved_to_abspaths = apr_array_make(result_pool, 1,
+                                         sizeof (const char *));
+
+      for (i = 0; i < candidate_abspaths->nelts; i++)
+        {
+          const char *candidate_abspath;
+          const char *repos_root_url;
+          const char *repos_uuid;
+          const char *candidate_repos_relpath;
+          svn_revnum_t candidate_revision;
+
+          svn_pool_clear(iterpool);
+
+          candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i,
+                                            const char *);
+          SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision,
+                                          &candidate_repos_relpath,
+                                          &repos_root_url,
+                                          &repos_uuid,
+                                          NULL, NULL,
+                                          ctx->wc_ctx,
+                                          candidate_abspath,
+                                          FALSE,
+                                          iterpool, iterpool));
+
+          if (candidate_revision == SVN_INVALID_REVNUM)
+            continue;
+
+          /* If the conflict victim and the move target candidate
+           * are not from the same revision we must ensure that
+           * they are related. */
+           if (candidate_revision != victim_revision)
+            {
+              svn_client__pathrev_t *yca_loc;
+              svn_error_t *err;
+
+              err = find_yca(&yca_loc, victim_repos_relpath,
+                             victim_revision,
+                             candidate_repos_relpath,
+                             candidate_revision,
+                             repos_root_url, repos_uuid,
+                             NULL, ctx, iterpool, iterpool);
+              if (err)
+                {
+                  if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+                    {
+                      svn_error_clear(err);
+                      yca_loc = NULL;
+                    }
+                  else
+                    return svn_error_trace(err);
+                }
+
+              if (yca_loc == NULL)
+                continue;
+            }
+
+          APR_ARRAY_PUSH(moved_to_abspaths, const char *) =
+            apr_pstrdup(result_pool, candidate_abspath);
+        }
+      svn_pool_destroy(iterpool);
+
+      svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath,
+                    moved_to_abspaths);
+    }
+
+  if (move->next)
+    {
+      int i;
+      apr_pool_t *iterpool;
+
+      /* Recurse into each of the possible move chains. */
+      iterpool = svn_pool_create(scratch_pool);
+      for (i = 0; i < move->next->nelts; i++)
+        {
+          struct repos_move_info *next_move;
+
+          svn_pool_clear(iterpool);
+
+          next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *);
+          SVN_ERR(follow_move_chains(wc_move_targets, next_move,
+                                     ctx, victim_abspath, victim_node_kind,
+                                     victim_repos_relpath, victim_revision,
+                                     result_pool, iterpool));
+                                        
+        }
+      svn_pool_destroy(iterpool);
+    }
+
+  return SVN_NO_ERROR;
+}
+
 /* Implements tree_conflict_get_details_func_t. */
 static svn_error_t *
 conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
@@ -2614,22 +2795,29 @@ conflict_tree_get_details_local_missing(
   svn_revnum_t old_rev;
   svn_revnum_t new_rev;
   svn_revnum_t deleted_rev;
+  svn_node_kind_t old_kind;
+  svn_node_kind_t new_kind;
   const char *deleted_rev_author;
   svn_node_kind_t replacing_node_kind;
   const char *deleted_basename;
   struct conflict_tree_local_missing_details *details;
   apr_array_header_t *moves = NULL;
   apr_array_header_t *sibling_moves = NULL;
+  apr_array_header_t *wc_siblings = NULL;
   const char *related_repos_relpath;
   svn_revnum_t related_peg_rev;
   const char *repos_root_url;
   const char *repos_uuid;
+  const char *url, *corrected_url;
+  svn_ra_session_t *ra_session;
+  svn_client__pathrev_t *yca_loc;
+  svn_revnum_t end_rev;
 
   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
-            &old_repos_relpath, &old_rev, NULL, conflict,
+            &old_repos_relpath, &old_rev, &old_kind, conflict,
             scratch_pool, scratch_pool));
   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
-            &new_repos_relpath, &new_rev, NULL, conflict,
+            &new_repos_relpath, &new_rev, &new_kind, conflict,
             scratch_pool, scratch_pool));
 
   /* Scan the conflict victim's parent's log to find a revision which
@@ -2645,6 +2833,11 @@ conflict_tree_get_details_local_missing(
                                       scratch_pool,
                                       scratch_pool));
 
+  /* If the parent is not part of the repository-side tree checked out
+   * into this working copy, then bail. We do not support this case yet. */
+  if (parent_peg_rev == SVN_INVALID_REVNUM)
+    return SVN_NO_ERROR;
+
   /* Pick the younger incoming node as our 'related node' which helps
    * pin-pointing the deleted conflict victim in history. */
   related_repos_relpath = 
@@ -2660,51 +2853,66 @@ conflict_tree_get_details_local_missing(
               (old_rev < new_rev ? old_repos_relpath : new_repos_relpath),
               (old_rev < new_rev ? old_rev : new_rev),
               conflict, ctx, scratch_pool, scratch_pool));
-    
+
+  /* Set END_REV to our best guess of the nearest YCA revision. */
+  url = svn_path_url_add_component2(repos_root_url, related_repos_relpath,
+                                    scratch_pool);
+  SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
+                                               &corrected_url,
+                                               url, NULL, NULL,
+                                               FALSE,
+                                               FALSE,
+                                               ctx,
+                                               scratch_pool,
+                                               scratch_pool));
+  SVN_ERR(find_nearest_yca(&yca_loc, related_repos_relpath, related_peg_rev,
+                           parent_repos_relpath, parent_peg_rev,
+                           repos_root_url, repos_uuid, ra_session, ctx,
+                           scratch_pool, scratch_pool));
+  if (yca_loc)
+   {
+     end_rev = yca_loc->rev;
+
+    /* END_REV must be smaller than PARENT_PEG_REV, else the call to
+     * find_revision_for_suspected_deletion() below will abort. */
+    if (end_rev >= parent_peg_rev)
+      end_rev = parent_peg_rev > 0 ? parent_peg_rev - 1 : 0;
+   }
+  else
+    end_rev = 0; /* ### We might walk through all of history... */
+
   SVN_ERR(find_revision_for_suspected_deletion(
-            &deleted_rev, &deleted_rev_author, &replacing_node_kind, &moves,
+            &deleted_rev, &deleted_rev_author, &replacing_node_kind,
+            yca_loc ? &moves : NULL,
             conflict, deleted_basename, parent_repos_relpath,
-            parent_peg_rev, 0, related_repos_relpath, related_peg_rev,
+            parent_peg_rev, end_rev, related_repos_relpath, related_peg_rev,
             ctx, conflict->pool, scratch_pool));
 
   /* If the victim was not deleted then check if the related path was moved. */
   if (deleted_rev == SVN_INVALID_REVNUM)
     {
       const char *victim_abspath;
-      svn_ra_session_t *ra_session;
-      const char *url, *corrected_url;
-      svn_client__pathrev_t *yca_loc;
-      svn_revnum_t end_rev;
       svn_node_kind_t related_node_kind;
+      apr_array_header_t *candidates;
+      int i;
+      apr_pool_t *iterpool;
 
       /* ### The following describes all moves in terms of forward-merges,
        * should do we something else for reverse-merges? */
 
       victim_abspath = svn_client_conflict_get_local_abspath(conflict);
-      url = svn_path_url_add_component2(repos_root_url, related_repos_relpath,
-                                        scratch_pool);
-      SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
-                                                   &corrected_url,
-                                                   url, NULL, NULL,
-                                                   FALSE,
-                                                   FALSE,
-                                                   ctx,
-                                                   scratch_pool,
-                                                   scratch_pool));
-
-      /* Set END_REV to our best guess of the nearest YCA revision. */
-      SVN_ERR(find_nearest_yca(&yca_loc, related_repos_relpath, related_peg_rev,
-                               parent_repos_relpath, parent_peg_rev,
-                               repos_root_url, repos_uuid, ra_session, ctx,
-                               scratch_pool, scratch_pool));
-      if (yca_loc == NULL)
-        return SVN_NO_ERROR;
-      end_rev = yca_loc->rev;
 
-      /* END_REV must be smaller than RELATED_PEG_REV, else the call
-         to find_moves_in_natural_history() below will error out. */
-      if (end_rev >= related_peg_rev)
-        end_rev = related_peg_rev > 0 ? related_peg_rev - 1 : 0;
+      if (yca_loc)
+       {
+          end_rev = yca_loc->rev;
+
+          /* END_REV must be smaller than RELATED_PEG_REV, else the call
+             to find_moves_in_natural_history() below will error out. */
+          if (end_rev >= related_peg_rev)
+            end_rev = related_peg_rev > 0 ? related_peg_rev - 1 : 0;
+       }
+      else
+        end_rev = 0; /* ### We might walk through all of history... */
 
       SVN_ERR(svn_ra_check_path(ra_session, "", related_peg_rev,
                                 &related_node_kind, scratch_pool));
@@ -2721,7 +2929,81 @@ conflict_tree_get_details_local_missing(
       if (sibling_moves == NULL)
         return SVN_NO_ERROR;
 
-      /* ## TODO: Find the missing node in the WC. */
+      /* Find the missing node in the WC. In theory, this requires tracing
+       * back history of every node in the WC to check for a YCA with the
+       * conflict victim. This operation would obviously be quite expensive.
+       *
+       * However, assuming that the victim was not moved in the merge target,
+       * we can take a short-cut: The basename of the node cannot have changed,
+       * so we can limit history tracing to nodes with a matching basename.
+       *
+       * This approach solves the conflict case where an edit to a file which
+       * was moved on one branch is cherry-picked to another branch where the
+       * corresponding file has not been moved (yet). It does not solve move
+       * vs. move conflicts, but such conflicts are not yet supported by the
+       * resolver anyway and are hard to solve without server-side support. */
+      iterpool = svn_pool_create(scratch_pool);
+      for (i = 0; i < sibling_moves->nelts; i++)
+        {
+          struct repos_move_info *move;
+          int j;
+
+          svn_pool_clear(iterpool);
+
+          move = APR_ARRAY_IDX(sibling_moves, i, struct repos_move_info *);
+          candidates = apr_array_make(iterpool, 1, sizeof(const char *));
+          SVN_ERR(collect_sibling_move_candidates(candidates, victim_abspath,
+                                                  old_rev < new_rev
+                                                    ? new_kind : old_kind,
+                                                  move, ctx, iterpool,
+                                                  iterpool));
+
+          /* Determine whether a candidate node shares a YCA with the victim. */
+          for (j = 0; j < candidates->nelts; j++)
+            {
+              const char *candidate_abspath;
+              const char *candidate_repos_relpath;
+              svn_revnum_t candidate_revision;
+              svn_error_t *err;
+
+              candidate_abspath = APR_ARRAY_IDX(candidates, j, const char *);
+              SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision,
+                                              &candidate_repos_relpath,
+                                              NULL, NULL, NULL, NULL,
+                                              ctx->wc_ctx,
+                                              candidate_abspath,
+                                              FALSE,
+                                              iterpool, iterpool));
+              err = find_yca(&yca_loc,
+                             old_rev < new_rev
+                               ? new_repos_relpath : old_repos_relpath,
+                             old_rev < new_rev ? new_rev : old_rev,
+                             candidate_repos_relpath,
+                             candidate_revision,
+                             repos_root_url, repos_uuid,
+                             NULL, ctx, iterpool, iterpool);
+              if (err)
+                {
+                  if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+                    {
+                      svn_error_clear(err);
+                      yca_loc = NULL;
+                    }
+                  else
+                    return svn_error_trace(err);
+                }
+
+              if (yca_loc)
+                {
+                  if (wc_siblings == NULL)
+                    wc_siblings = apr_array_make(conflict->pool, 1,
+                                                 sizeof(const char *));
+                  APR_ARRAY_PUSH(wc_siblings, const char *) =
+                    apr_pstrdup(conflict->pool, candidate_abspath);
+                }
+            }
+        }
+      svn_pool_destroy(iterpool);
     }
 
   details = apr_pcalloc(conflict->pool, sizeof(*details));
@@ -2732,26 +3014,101 @@ conflict_tree_get_details_local_missing(
                                                       deleted_basename,
                                                       conflict->pool); 
   details->moves = moves;
-  details->sibling_moves = sibling_moves;
-                                         
-  conflict->tree_conflict_local_details = details;
+  if (details->moves != NULL)
+    {
+      apr_pool_t *iterpool;
+      int i;
 
-  return SVN_NO_ERROR;
-}
+      details->wc_move_targets = apr_hash_make(conflict->pool);
+      iterpool = svn_pool_create(scratch_pool);
+      for (i = 0; i < details->moves->nelts; i++)
+        {
+          struct repos_move_info *move;
 
-/* Return a localised string representation of the local part of a tree
-   conflict on a non-existent node. */
-static svn_error_t *
-describe_local_none_node_change(const char **description,
-                                svn_client_conflict_t *conflict,
-                                apr_pool_t *result_pool,
-                                apr_pool_t *scratch_pool)
-{
-  svn_wc_conflict_reason_t local_change;
-  svn_wc_operation_t operation;
+          svn_pool_clear(iterpool);
+          move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *);
+          SVN_ERR(follow_move_chains(details->wc_move_targets, move, ctx,
+                                     conflict->local_abspath,
+                                     new_kind,
+                                     new_repos_relpath,
+                                     new_rev,
+                                     scratch_pool, iterpool));
+        }
+      svn_pool_destroy(iterpool);
 
-  local_change = svn_client_conflict_get_local_change(conflict);
-  operation = svn_client_conflict_get_operation(conflict);
+      if (apr_hash_count(details->wc_move_targets) > 0)
+        {
+          apr_array_header_t *move_target_repos_relpaths;
+          const svn_sort__item_t *item;
+
+          /* Initialize to the first possible move target. Hopefully,
+           * in most cases there will only be one candidate anyway. */
+          move_target_repos_relpaths = svn_sort__hash(
+                                         details->wc_move_targets,
+                                         svn_sort_compare_items_as_paths,
+                                         scratch_pool);
+          item = &APR_ARRAY_IDX(move_target_repos_relpaths,
+                                0, svn_sort__item_t);
+          details->move_target_repos_relpath = item->key;
+          details->wc_move_target_idx = 0;
+        }
+      else
+        {
+          details->move_target_repos_relpath = NULL;
+          details->wc_move_target_idx = 0;
+        }
+    }
+                                         
+  details->sibling_moves = sibling_moves;
+  details->wc_siblings = wc_siblings;
+  if (details->wc_move_targets && apr_hash_count(details->wc_move_targets) == 1)
+    {
+      apr_array_header_t *wc_abspaths;
+
+      wc_abspaths = svn_hash_gets(details->wc_move_targets,
+                                  details->move_target_repos_relpath);
+      if (wc_abspaths->nelts == 1)
+        {
+          svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind;
+
+          if (kind == svn_node_file)
+              conflict->recommended_option_id =
+                  svn_client_conflict_option_local_move_file_text_merge;
+          else if (kind == svn_node_dir)
+              conflict->recommended_option_id =
+                svn_client_conflict_option_local_move_dir_merge;
+      }
+    }
+  else if (details->wc_siblings && details->wc_siblings->nelts == 1)
+    {
+      svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind;
+
+      if (kind == svn_node_file)
+          conflict->recommended_option_id =
+              svn_client_conflict_option_sibling_move_file_text_merge;
+      else if (kind == svn_node_dir)
+          conflict->recommended_option_id =
+            svn_client_conflict_option_sibling_move_dir_merge;
+    }
+
+  conflict->tree_conflict_local_details = details;
+
+  return SVN_NO_ERROR;
+}
+
+/* Return a localised string representation of the local part of a tree
+   conflict on a non-existent node. */
+static svn_error_t *
+describe_local_none_node_change(const char **description,
+                                svn_client_conflict_t *conflict,
+                                apr_pool_t *result_pool,
+                                apr_pool_t *scratch_pool)
+{
+  svn_wc_conflict_reason_t local_change;
+  svn_wc_operation_t operation;
+
+  local_change = svn_client_conflict_get_local_change(conflict);
+  operation = svn_client_conflict_get_operation(conflict);
 
   switch (local_change)
     {
@@ -4642,133 +4999,6 @@ get_incoming_delete_details_for_reverse_
   return SVN_NO_ERROR;
 }
 
-/* Follow each move chain starting a MOVE all the way to the end to find
- * the possible working copy locations for VICTIM_ABSPATH which corresponds
- * to VICTIM_REPOS_REPLATH@VICTIM_REVISION.
- * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the
- * repos_relpath which is the corresponding move destination in the repository.
- * This function is recursive. */
-static svn_error_t *
-follow_move_chains(apr_hash_t *wc_move_targets,
-                   struct repos_move_info *move,
-                   svn_client_ctx_t *ctx,
-                   const char *victim_abspath,
-                   svn_node_kind_t victim_node_kind,
-                   const char *victim_repos_relpath,
-                   svn_revnum_t victim_revision,
-                   apr_pool_t *result_pool,
-                   apr_pool_t *scratch_pool)
-{
-  /* If this is the end of a move chain, look for matching paths in
-   * the working copy and add them to our collection if found. */
-  if (move->next == NULL)
-    {
-      apr_array_header_t *candidate_abspaths;
-
-      /* Gather candidate nodes which represent this moved_to_repos_relpath. */
-      SVN_ERR(svn_wc__guess_incoming_move_target_nodes(
-                &candidate_abspaths, ctx->wc_ctx,
-                victim_abspath, victim_node_kind,
-                move->moved_to_repos_relpath,
-                scratch_pool, scratch_pool));
-      if (candidate_abspaths->nelts > 0)
-        {
-          apr_array_header_t *moved_to_abspaths;
-          int i;
-          apr_pool_t *iterpool = svn_pool_create(scratch_pool);
-
-          moved_to_abspaths = apr_array_make(result_pool, 1,
-                                             sizeof (const char *));
-
-          for (i = 0; i < candidate_abspaths->nelts; i++)
-            {
-              const char *candidate_abspath;
-              const char *repos_root_url;
-              const char *repos_uuid;
-              const char *candidate_repos_relpath;
-              svn_revnum_t candidate_revision;
-
-              svn_pool_clear(iterpool);
-
-              candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i,
-                                                const char *);
-              SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision,
-                                              &candidate_repos_relpath,
-                                              &repos_root_url,
-                                              &repos_uuid,
-                                              NULL, NULL,
-                                              ctx->wc_ctx,
-                                              candidate_abspath,
-                                              FALSE,
-                                              iterpool, iterpool));
-
-              if (candidate_revision == SVN_INVALID_REVNUM)
-                continue;
-
-              /* If the conflict victim and the move target candidate
-               * are not from the same revision we must ensure that
-               * they are related. */
-               if (candidate_revision != victim_revision)
-                {
-                  svn_client__pathrev_t *yca_loc;
-                  svn_error_t *err;
-
-                  err = find_yca(&yca_loc, victim_repos_relpath,
-                                 victim_revision,
-                                 candidate_repos_relpath,
-                                 candidate_revision,
-                                 repos_root_url, repos_uuid,
-                                 NULL, ctx, iterpool, iterpool);
-                  if (err)
-                    {
-                      if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
-                        {
-                          svn_error_clear(err);
-                          yca_loc = NULL;
-                        }
-                      else
-                        return svn_error_trace(err);
-                    }
-
-                  if (yca_loc == NULL)
-                    continue;
-                }
-
-              APR_ARRAY_PUSH(moved_to_abspaths, const char *) =
-                apr_pstrdup(result_pool, candidate_abspath);
-            }
-          svn_pool_destroy(iterpool);
-
-          svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath,
-                        moved_to_abspaths);
-        }
-    }
-  else
-    {
-      int i;
-      apr_pool_t *iterpool;
-
-      /* Recurse into each of the possible move chains. */
-      iterpool = svn_pool_create(scratch_pool);
-      for (i = 0; i < move->next->nelts; i++)
-        {
-          struct repos_move_info *next_move;
-
-          svn_pool_clear(iterpool);
-
-          next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *);
-          SVN_ERR(follow_move_chains(wc_move_targets, next_move,
-                                     ctx, victim_abspath, victim_node_kind,
-                                     victim_repos_relpath, victim_revision,
-                                     result_pool, iterpool));
-                                        
-        }
-      svn_pool_destroy(iterpool);
-    }
-
-  return SVN_NO_ERROR;
-}
-
 static svn_error_t *
 init_wc_move_targets(struct conflict_tree_incoming_delete_details *details,
                      svn_client_conflict_t *conflict,
@@ -4780,11 +5010,9 @@ init_wc_move_targets(struct conflict_tre
   svn_node_kind_t victim_node_kind;
   const char *incoming_new_repos_relpath;
   svn_revnum_t incoming_new_pegrev;
-  svn_wc_operation_t operation;
 
   victim_abspath = svn_client_conflict_get_local_abspath(conflict);
   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
-  operation = svn_client_conflict_get_operation(conflict);
   /* ### Should we get the old location in case of reverse-merges? */
   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
             &incoming_new_repos_relpath, &incoming_new_pegrev,
@@ -4810,11 +5038,8 @@ init_wc_move_targets(struct conflict_tre
     get_moved_to_repos_relpath(details, scratch_pool);
   details->wc_move_target_idx = 0;
 
-  /* If only one move target exists after an update or switch,
-   * recommend a resolution option which follows the incoming move. */
-  if (apr_hash_count(details->wc_move_targets) == 1 &&
-      (operation == svn_wc_operation_update ||
-       operation == svn_wc_operation_switch))
+  /* If only one move target exists recommend a resolution option. */
+  if (apr_hash_count(details->wc_move_targets) == 1)
     {
       apr_array_header_t *wc_abspaths;
 
@@ -4827,7 +5052,10 @@ init_wc_move_targets(struct conflict_tre
               /* Only one of these will be present for any given conflict. */
               svn_client_conflict_option_incoming_move_file_text_merge,
               svn_client_conflict_option_incoming_move_dir_merge,
-              svn_client_conflict_option_local_move_file_text_merge
+              svn_client_conflict_option_local_move_file_text_merge,
+              svn_client_conflict_option_local_move_dir_merge,
+              svn_client_conflict_option_sibling_move_file_text_merge,
+              svn_client_conflict_option_sibling_move_dir_merge,
             };
           apr_array_header_t *options;
 
@@ -4884,6 +5112,7 @@ conflict_tree_get_details_incoming_delet
           const char *parent_repos_relpath;
           svn_revnum_t parent_peg_rev;
           svn_revnum_t deleted_rev;
+          svn_revnum_t end_rev;
           const char *deleted_rev_author;
           svn_node_kind_t replacing_node_kind;
           apr_array_header_t *moves;
@@ -4916,12 +5145,15 @@ conflict_tree_get_details_incoming_delet
               related_peg_rev = SVN_INVALID_REVNUM;
             }
 
+          end_rev = (new_kind == svn_node_none ? 0 : old_rev);
+          if (end_rev >= parent_peg_rev)
+            end_rev = (parent_peg_rev > 0 ? parent_peg_rev - 1 : 0);
+
           SVN_ERR(find_revision_for_suspected_deletion(
                     &deleted_rev, &deleted_rev_author, &replacing_node_kind,
                     &moves, conflict,
                     svn_dirent_basename(conflict->local_abspath, scratch_pool),
-                    parent_repos_relpath, parent_peg_rev,
-                    new_kind == svn_node_none ? 0 : old_rev,
+                    parent_repos_relpath, parent_peg_rev, end_rev,
                     related_repos_relpath, related_peg_rev,
                     ctx, conflict->pool, scratch_pool));
           if (deleted_rev == SVN_INVALID_REVNUM)
@@ -5054,7 +5286,7 @@ conflict_tree_get_details_incoming_add(s
   const char *repos_root_url;
   svn_revnum_t old_rev;
   svn_revnum_t new_rev;
-  struct conflict_tree_incoming_add_details *details;
+  struct conflict_tree_incoming_add_details *details = NULL;
   svn_wc_operation_t operation;
 
   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
@@ -5147,7 +5379,8 @@ conflict_tree_get_details_incoming_add(s
             }
         }
     }
-  else if (operation == svn_wc_operation_merge)
+  else if (operation == svn_wc_operation_merge &&
+           strcmp(old_repos_relpath, new_repos_relpath) == 0)
     {
       if (old_rev < new_rev)
         {
@@ -5198,7 +5431,7 @@ conflict_tree_get_details_incoming_add(s
           details->deleted_rev = SVN_INVALID_REVNUM;
           details->deleted_rev_author = NULL;
         }
-      else
+      else if (old_rev > new_rev)
         {
           /* The merge operation was a reverse-merge.
            * This addition is in fact a deletion, applied in reverse,
@@ -5238,10 +5471,6 @@ conflict_tree_get_details_incoming_add(s
           details->moves = moves;
         }
     }
-  else
-    {
-      details = NULL;
-    }
 
   conflict->tree_conflict_incoming_details = details;
 
@@ -5704,7 +5933,10 @@ find_modified_rev(void *baton,
 
           if (log_item->copyfrom_path)
             b->repos_relpath = apr_pstrdup(b->scratch_pool,
-                                           log_item->copyfrom_path);
+                                          /* ### remove leading slash */
+                                           svn_relpath_canonicalize(
+                                               log_item->copyfrom_path,
+                                               iterpool));
         }
       else if (b->node_kind == svn_node_dir &&
                svn_relpath_skip_ancestor(b->repos_relpath, path) != NULL)
@@ -5908,6 +6140,9 @@ describe_incoming_edit_list_modified_rev
   const char *s = "";
   int i;
 
+  if (edits->nelts == 0)
+    return _(" (no revisions found)");
+
   if (edits->nelts <= max_revs_to_display)
     num_revs_to_skip = 0;
   else
@@ -5944,8 +6179,11 @@ describe_incoming_edit_list_modified_rev
             {
               if (i == edits->nelts - (max_revs_to_display / 2))
                   s = apr_psprintf(result_pool,
-                                   _("%s\n [%d revisions omitted for "
-                                     "brevity],\n"),
+                                   Q_("%s\n [%d revision omitted for "
+                                      "brevity],\n",
+                                      "%s\n [%d revisions omitted for "
+                                      "brevity],\n",
+                                      num_revs_to_skip),
                                    s, num_revs_to_skip);
 
               s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
@@ -6660,8 +6898,10 @@ resolve_merge_incoming_added_file_text_u
   apr_hash_t *working_props;
   apr_array_header_t *propdiffs;
   svn_error_t *err;
+  svn_wc_conflict_reason_t local_change;
 
   local_abspath = svn_client_conflict_get_local_abspath(conflict);
+  local_change = svn_client_conflict_get_local_change(conflict);
 
   /* Set up tempory storage for the working version of file. */
   SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
@@ -6672,20 +6912,31 @@ resolve_merge_incoming_added_file_text_u
                                  svn_io_file_del_none,
                                  scratch_pool, scratch_pool));
 
-  /* Copy the detranslated working file to temporary storage. */
-  SVN_ERR(svn_wc__translated_stream(&working_file_stream, ctx->wc_ctx,
-                                    local_abspath, local_abspath,
-                                    SVN_WC_TRANSLATE_TO_NF,
-                                    scratch_pool, scratch_pool));
+  if (local_change == svn_wc_conflict_reason_unversioned)
+    {
+      /* Copy the unversioned file to temporary storage. */
+      SVN_ERR(svn_stream_open_readonly(&working_file_stream, local_abspath,
+                                       scratch_pool, scratch_pool));
+      /* Unversioned files have no properties. */
+      working_props = apr_hash_make(scratch_pool);
+    }
+  else
+    {
+      /* Copy the detranslated working file to temporary storage. */
+      SVN_ERR(svn_wc__translated_stream(&working_file_stream, ctx->wc_ctx,
+                                        local_abspath, local_abspath,
+                                        SVN_WC_TRANSLATE_TO_NF,
+                                        scratch_pool, scratch_pool));
+      /* Get a copy of the working file's properties. */
+      SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath,
+                                scratch_pool, scratch_pool));
+      filter_props(working_props, scratch_pool);
+    }
+
   SVN_ERR(svn_stream_copy3(working_file_stream, working_file_tmp_stream,
                            ctx->cancel_func, ctx->cancel_baton,
                            scratch_pool));
 
-  /* Get a copy of the working file's properties. */
-  SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath,
-                            scratch_pool, scratch_pool));
-  filter_props(working_props, scratch_pool);
-
   /* Create an empty file as fake "merge-base" for the two added files.
    * The files are not ancestrally related so this is the best we can do. */
   SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file_abspath, NULL,
@@ -6704,8 +6955,9 @@ resolve_merge_incoming_added_file_text_u
   /* Revert the path in order to restore the repository's line of
    * history, which is part of the BASE tree. This revert operation
    * is why are being careful about not losing the temporary copy. */
-  err = svn_wc_revert5(ctx->wc_ctx, local_abspath, svn_depth_empty,
+  err = svn_wc_revert6(ctx->wc_ctx, local_abspath, svn_depth_empty,
                        FALSE, NULL, TRUE, FALSE,
+                       TRUE /*added_keep_local*/,
                        NULL, NULL, /* no cancellation */
                        ctx->notify_func2, ctx->notify_baton2,
                        scratch_pool);
@@ -7522,7 +7774,6 @@ merge_newly_added_dir(const char *added_
   diff_processor = processor;
   if (reverse_merge)
     diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
-                                                             NULL,
                                                              scratch_pool);
 
   /* Filter the first path component using a filter processor, until we fixed
@@ -7684,20 +7935,47 @@ resolve_update_incoming_added_dir_merge(
   const char *local_abspath;
   const char *lock_abspath;
   svn_error_t *err;
+  svn_wc_conflict_reason_t local_change;
 
   local_abspath = svn_client_conflict_get_local_abspath(conflict);
+  local_change = svn_client_conflict_get_local_change(conflict);
 
-  SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
-            &lock_abspath, ctx->wc_ctx, local_abspath,
-            scratch_pool, scratch_pool));
+  if (local_change == svn_wc_conflict_reason_unversioned)
+    {
+      char *parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+      SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
+                &lock_abspath, ctx->wc_ctx, parent_abspath,
+                scratch_pool, scratch_pool));
 
-  err = svn_wc__conflict_tree_update_local_add(ctx->wc_ctx,
-                                               local_abspath,
-                                               ctx->cancel_func,
-                                               ctx->cancel_baton,
-                                               ctx->notify_func2,
-                                               ctx->notify_baton2,
-                                               scratch_pool);
+      /* The update/switch operation has added the incoming versioned
+       * directory as a deleted op-depth layer. We can revert this layer
+       * to make the incoming tree appear in the working copy.
+       * This meta-data-only revert operation effecively merges the
+       * versioned and unversioned trees but leaves all unversioned files as
+       * they were. This is the best we can do; 3-way merging of unversioned
+       * files with files from the repository is impossible because there is
+       * no known merge base. No unversioned data will be lost, and any
+       * differences to files in the repository will show up in 'svn diff'. */
+      err = svn_wc_revert6(ctx->wc_ctx, local_abspath, svn_depth_infinity,
+                           FALSE, NULL, TRUE, TRUE /* metadata_only */,
+                           TRUE /*added_keep_local*/,
+                           NULL, NULL, /* no cancellation */
+                           ctx->notify_func2, ctx->notify_baton2,
+                           scratch_pool);
+    }
+  else
+    {
+      SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
+                &lock_abspath, ctx->wc_ctx, local_abspath,
+                scratch_pool, scratch_pool));
+      err = svn_wc__conflict_tree_update_local_add(ctx->wc_ctx,
+                                                   local_abspath,
+                                                   ctx->cancel_func,
+                                                   ctx->cancel_baton,
+                                                   ctx->notify_func2,
+                                                   ctx->notify_baton2,
+                                                   scratch_pool);
+    }
 
   err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
                                                                  lock_abspath,
@@ -7707,35 +7985,6 @@ resolve_update_incoming_added_dir_merge(
   return SVN_NO_ERROR;
 }
 
-/* A baton for notification_adjust_func(). */
-struct notification_adjust_baton
-{
-  svn_wc_notify_func2_t inner_func;
-  void *inner_baton;
-  const char *checkout_abspath;
-  const char *final_abspath;
-};
-
-/* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
- * baton is BATON->inner_baton) and adjusts the notification paths that
- * start with BATON->checkout_abspath to start instead with
- * BATON->final_abspath. */
-static void
-notification_adjust_func(void *baton,
-                         const svn_wc_notify_t *notify,
-                         apr_pool_t *pool)
-{
-  struct notification_adjust_baton *nb = baton;
-  svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
-  const char *relpath;
-
-  relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
-  inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
-
-  if (nb->inner_func)
-    nb->inner_func(nb->inner_baton, inner_notify, pool);
-}
-
 /* Resolve a dir/dir "incoming add vs local obstruction" tree conflict by
  * replacing the local directory with the incoming directory.
  * If MERGE_DIRS is set, also merge the directories after replacing. */
@@ -7754,14 +8003,8 @@ merge_incoming_added_dir_replace(svn_cli
   svn_revnum_t incoming_new_pegrev;
   const char *local_abspath;
   const char *lock_abspath;
-  const char *tmpdir_abspath, *tmp_abspath;
   svn_error_t *err;
-  svn_revnum_t copy_src_revnum;
-  svn_opt_revision_t copy_src_peg_revision;
   svn_boolean_t timestamp_sleep;
-  svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
-  void *old_notify_baton2 = ctx->notify_baton2;
-  struct notification_adjust_baton nb;
 
   local_abspath = svn_client_conflict_get_local_abspath(conflict);
 
@@ -7782,46 +8025,6 @@ merge_incoming_added_dir_replace(svn_cli
   if (corrected_url)
     url = corrected_url;
 
-
-  /* Find a temporary location in which to check out the copy source. */
-  SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, local_abspath,
-                             scratch_pool, scratch_pool));
-
-  SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
-                                   svn_io_file_del_on_close,
-                                   scratch_pool, scratch_pool));
-
-  /* Make a new checkout of the requested source. While doing so,
-   * resolve copy_src_revnum to an actual revision number in case it
-   * was until now 'invalid' meaning 'head'.  Ask this function not to
-   * sleep for timestamps, by passing a sleep_needed output param.
-   * Send notifications for all nodes except the root node, and adjust
-   * them to refer to the destination rather than this temporary path. */
-
-  nb.inner_func = ctx->notify_func2;
-  nb.inner_baton = ctx->notify_baton2;
-  nb.checkout_abspath = tmp_abspath;
-  nb.final_abspath = local_abspath;
-  ctx->notify_func2 = notification_adjust_func;
-  ctx->notify_baton2 = &nb;
-
-  copy_src_peg_revision.kind = svn_opt_revision_number;
-  copy_src_peg_revision.value.number = incoming_new_pegrev;
-
-  err = svn_client__checkout_internal(&copy_src_revnum, &timestamp_sleep,
-                                      url, tmp_abspath,
-                                      &copy_src_peg_revision,
-                                      &copy_src_peg_revision,
-                                      svn_depth_infinity,
-                                      TRUE, /* we want to ignore externals */
-                                      FALSE, /* we don't allow obstructions */
-                                      ra_session, ctx, scratch_pool);
-
-  ctx->notify_func2 = old_notify_func2;
-  ctx->notify_baton2 = old_notify_baton2;
-
-  SVN_ERR(err);
-
   /* ### The following WC modifications should be atomic. */
 
   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
@@ -7838,31 +8041,13 @@ merge_incoming_added_dir_replace(svn_cli
   if (err)
     goto unlock_wc;
 
-  /* Schedule dst_path for addition in parent, with copy history.
-     Don't send any notification here.
-     Then remove the temporary checkout's .svn dir in preparation for
-     moving the rest of it into the final destination. */
-  err = svn_wc_copy3(ctx->wc_ctx, tmp_abspath, local_abspath,
-                     TRUE /* metadata_only */,
-                     NULL, NULL, /* don't allow user to cancel here */
-                     NULL, NULL, scratch_pool);
-  if (err)
-    goto unlock_wc;
-
-  err = svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
-                                   FALSE, scratch_pool, scratch_pool);
-  if (err)
-    goto unlock_wc;
-  err = svn_wc_remove_from_revision_control2(ctx->wc_ctx,
-                                             tmp_abspath,
-                                             FALSE, FALSE,
-                                             NULL, NULL, /* don't cancel */
-                                             scratch_pool);
-  if (err)
-    goto unlock_wc;
-
-  /* Move the temporary disk tree into place. */
-  err = svn_io_file_rename2(tmp_abspath, local_abspath, FALSE, scratch_pool);
+  err = svn_client__repos_to_wc_copy_dir(&timestamp_sleep,
+                                         url,
+                                         incoming_new_pegrev,
+                                         local_abspath,
+                                         TRUE, /* we want to ignore externals */
+                                         TRUE /*same_repositories*/,
+                                         ra_session, ctx, scratch_pool);
   if (err)
     goto unlock_wc;
 
@@ -7989,6 +8174,112 @@ resolve_merge_incoming_added_dir_replace
                                                           scratch_pool));
 }
 
+/* Ensure the conflict victim is a copy of itself from before it was deleted.
+ * Update and switch are supposed to set this up when flagging the conflict. */
+static svn_error_t *
+ensure_local_edit_vs_incoming_deletion_copied_state(
+  struct conflict_tree_incoming_delete_details *details,
+  svn_wc_operation_t operation,
+  const char *wcroot_abspath,
+  svn_client_conflict_t *conflict,
+  svn_client_ctx_t *ctx,
+  apr_pool_t *scratch_pool)
+{
+
+  svn_boolean_t is_copy;
+  svn_revnum_t copyfrom_rev;
+  const char *copyfrom_repos_relpath;
+
+  SVN_ERR_ASSERT(operation == svn_wc_operation_update ||
+                 operation == svn_wc_operation_switch);
+  
+  SVN_ERR(svn_wc__node_get_origin(&is_copy, &copyfrom_rev,
+                                  &copyfrom_repos_relpath,
+                                  NULL, NULL, NULL, NULL,
+                                  ctx->wc_ctx, conflict->local_abspath,
+                                  FALSE, scratch_pool, scratch_pool));
+  if (!is_copy)
+    return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                             _("Cannot resolve tree conflict on '%s' "
+                               "(expected a copied item, but the item "
+                               "is not a copy)"),
+                             svn_dirent_local_style(
+                               svn_dirent_skip_ancestor(
+                                 wcroot_abspath,
+                                 conflict->local_abspath),
+                             scratch_pool));
+  else if (details->deleted_rev != SVN_INVALID_REVNUM &&
+           copyfrom_rev >= details->deleted_rev)
+    return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                             _("Cannot resolve tree conflict on '%s' "
+                               "(expected an item copied from a revision "
+                               "smaller than r%ld, but the item was "
+                               "copied from r%ld)"),
+                             svn_dirent_local_style(
+                               svn_dirent_skip_ancestor(
+                                 wcroot_abspath, conflict->local_abspath),
+                               scratch_pool),
+                             details->deleted_rev, copyfrom_rev);
+  else if (details->added_rev != SVN_INVALID_REVNUM &&
+           copyfrom_rev < details->added_rev)
+    return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                             _("Cannot resolve tree conflict on '%s' "
+                               "(expected an item copied from a revision "
+                               "larger than r%ld, but the item was "
+                               "copied from r%ld)"),
+                             svn_dirent_local_style(
+                               svn_dirent_skip_ancestor(
+                                 wcroot_abspath, conflict->local_abspath),
+                               scratch_pool),
+                              details->added_rev, copyfrom_rev);
+  else if (operation == svn_wc_operation_update)
+    {
+      const char *old_repos_relpath;
+
+      SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+                &old_repos_relpath, NULL, NULL, conflict,
+                scratch_pool, scratch_pool));
+      if (strcmp(copyfrom_repos_relpath, details->repos_relpath) != 0 &&
+          strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0)
+        return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                                 _("Cannot resolve tree conflict on '%s' "
+                                   "(expected an item copied from '^/%s' "
+                                   "or from '^/%s' but the item was "
+                                   "copied from '^/%s@%ld')"),
+                                 svn_dirent_local_style(
+                                   svn_dirent_skip_ancestor(
+                                     wcroot_abspath, conflict->local_abspath),
+                                   scratch_pool),
+                                 details->repos_relpath,
+                                 old_repos_relpath,
+                                 copyfrom_repos_relpath, copyfrom_rev);
+    }
+  else if (operation == svn_wc_operation_switch)
+    {
+      const char *old_repos_relpath;
+
+      SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+                &old_repos_relpath, NULL, NULL, conflict,
+                scratch_pool, scratch_pool));
+
+      if (strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0)
+        return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                                 _("Cannot resolve tree conflict on '%s' "
+                                   "(expected an item copied from '^/%s', "
+                                   "but the item was copied from "
+                                    "'^/%s@%ld')"),
+                                 svn_dirent_local_style(
+                                   svn_dirent_skip_ancestor(
+                                     wcroot_abspath,
+                                     conflict->local_abspath),
+                                   scratch_pool),
+                                 old_repos_relpath,
+                                 copyfrom_repos_relpath, copyfrom_rev);
+    }
+
+  return SVN_NO_ERROR;
+}
+
 /* Verify the local working copy state matches what we expect when an
  * incoming deletion tree conflict exists.
  * We assume update/merge/switch operations leave the working copy in a
@@ -7999,26 +8290,25 @@ resolve_merge_incoming_added_dir_replace
 static svn_error_t *
 verify_local_state_for_incoming_delete(svn_client_conflict_t *conflict,
                                        svn_client_conflict_option_t *option,
-                                        svn_client_ctx_t *ctx,
+                                       svn_client_ctx_t *ctx,
                                        apr_pool_t *scratch_pool)
 {
   const char *local_abspath;
   const char *wcroot_abspath;
   svn_wc_operation_t operation;
+  svn_wc_conflict_reason_t local_change;
 
   local_abspath = svn_client_conflict_get_local_abspath(conflict);
   SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
                              local_abspath, scratch_pool,
                              scratch_pool));
   operation = svn_client_conflict_get_operation(conflict);
+  local_change = svn_client_conflict_get_local_change(conflict);
 
   if (operation == svn_wc_operation_update ||
       operation == svn_wc_operation_switch)
     {
       struct conflict_tree_incoming_delete_details *details;
-      svn_boolean_t is_copy;
-      svn_revnum_t copyfrom_rev;
-      const char *copyfrom_repos_relpath;
 
       details = conflict->tree_conflict_incoming_details;
       if (details == NULL)
@@ -8030,103 +8320,21 @@ verify_local_state_for_incoming_delete(s
                                 svn_dirent_local_style(local_abspath,
                                                        scratch_pool));
 
-      /* Ensure that the item is a copy of itself from before it was deleted.
-       * Update and switch are supposed to set this up when flagging the
-       * conflict. */
-      SVN_ERR(svn_wc__node_get_origin(&is_copy, &copyfrom_rev,
-                                      &copyfrom_repos_relpath,
-                                      NULL, NULL, NULL, NULL,
-                                      ctx->wc_ctx, local_abspath, FALSE,
-                                      scratch_pool, scratch_pool));
-      if (!is_copy)
+      if (details->deleted_rev == SVN_INVALID_REVNUM &&
+          details->added_rev == SVN_INVALID_REVNUM)
         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                                 _("Cannot resolve tree conflict on '%s' "
-                                   "(expected a copied item, but the item "
-                                   "is not a copy)"),
-                                 svn_dirent_local_style(
-                                   svn_dirent_skip_ancestor(
-                                     wcroot_abspath,
-                                     conflict->local_abspath),
-                                 scratch_pool));
-      else if (details->deleted_rev == SVN_INVALID_REVNUM &&
-               details->added_rev == SVN_INVALID_REVNUM)
-        return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                                 _("Could not find the revision in which '%s' "
-                                   "was deleted from the repository"),
+                                 _("Could not find the revision in which '%s' "
+                                   "was deleted from the repository"),
                                  svn_dirent_local_style(
                                    svn_dirent_skip_ancestor(
                                      wcroot_abspath,
                                      conflict->local_abspath),
                                    scratch_pool));
-      else if (details->deleted_rev != SVN_INVALID_REVNUM &&
-               copyfrom_rev >= details->deleted_rev)
-        return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                                 _("Cannot resolve tree conflict on '%s' "
-                                   "(expected an item copied from a revision "
-                                   "smaller than r%ld, but the item was "
-                                   "copied from r%ld)"),
-                                 svn_dirent_local_style(
-                                   svn_dirent_skip_ancestor(
-                                     wcroot_abspath, conflict->local_abspath),
-                                   scratch_pool),
-                                 details->deleted_rev, copyfrom_rev);
-
-      else if (details->added_rev != SVN_INVALID_REVNUM &&
-               copyfrom_rev < details->added_rev)
-        return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                                 _("Cannot resolve tree conflict on '%s' "
-                                   "(expected an item copied from a revision "
-                                   "larger than r%ld, but the item was "
-                                   "copied from r%ld)"),
-                                 svn_dirent_local_style(
-                                   svn_dirent_skip_ancestor(
-                                     wcroot_abspath, conflict->local_abspath),
-                                   scratch_pool),
-                                  details->added_rev, copyfrom_rev);
-      else if (operation == svn_wc_operation_update)
-        {
-          const char *old_repos_relpath;
 
-          SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
-                    &old_repos_relpath, NULL, NULL, conflict,
-                    scratch_pool, scratch_pool));
-          if (strcmp(copyfrom_repos_relpath, details->repos_relpath) != 0 &&
-              strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0)
-            return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                                     _("Cannot resolve tree conflict on '%s' "
-                                       "(expected an item copied from '^/%s' "
-                                       "or from '^/%s' but the item was "
-                                       "copied from '^/%s@%ld')"),
-                                     svn_dirent_local_style(
-                                       svn_dirent_skip_ancestor(
-                                         wcroot_abspath, conflict->local_abspath),
-                                       scratch_pool),
-                                     details->repos_relpath,
-                                     old_repos_relpath,
-                                     copyfrom_repos_relpath, copyfrom_rev);
-        }
-      else if (operation == svn_wc_operation_switch)
-        {
-          const char *old_repos_relpath;
-
-          SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
-                    &old_repos_relpath, NULL, NULL, conflict,
-                    scratch_pool, scratch_pool));
-
-          if (strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0)
-            return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                                     _("Cannot resolve tree conflict on '%s' "
-                                       "(expected an item copied from '^/%s', "
-                                       "but the item was copied from "
-                                        "'^/%s@%ld')"),
-                                     svn_dirent_local_style(
-                                       svn_dirent_skip_ancestor(
-                                         wcroot_abspath,
-                                         conflict->local_abspath),
-                                       scratch_pool),
-                                     old_repos_relpath,
-                                     copyfrom_repos_relpath, copyfrom_rev);
-        }
+      if (local_change == svn_wc_conflict_reason_edited)
+        SVN_ERR(ensure_local_edit_vs_incoming_deletion_copied_state(
+                  details, operation, wcroot_abspath, conflict, ctx,
+                  scratch_pool));
     }
   else if (operation == svn_wc_operation_merge)
     {
@@ -8276,7 +8484,9 @@ resolve_incoming_move_file_text_merge(sv
                                       apr_pool_t *scratch_pool)
 {
   svn_client_conflict_option_id_t option_id;
-  const char *local_abspath;
+  const char *victim_abspath;
+  const char *merge_source_abspath;
+  svn_wc_conflict_reason_t local_change;
   svn_wc_operation_t operation;
   const char *lock_abspath;
   svn_error_t *err;
@@ -8302,7 +8512,8 @@ resolve_incoming_move_file_text_merge(sv
   const char *moved_to_abspath;
   const char *incoming_abspath = NULL;
 
-  local_abspath = svn_client_conflict_get_local_abspath(conflict);
+  victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+  local_change = svn_client_conflict_get_local_change(conflict);
   operation = svn_client_conflict_get_operation(conflict);
   details = conflict->tree_conflict_incoming_details;
   if (details == NULL || details->moves == NULL)
@@ -8310,20 +8521,20 @@ resolve_incoming_move_file_text_merge(sv
                              _("The specified conflict resolution option "
                                "requires details for tree conflict at '%s' "
                                "to be fetched from the repository first."),
-                            svn_dirent_local_style(local_abspath,
+                            svn_dirent_local_style(victim_abspath,
                                                    scratch_pool));
   if (operation == svn_wc_operation_none)
     return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
                              _("Invalid operation code '%d' recorded for "
                                "conflict at '%s'"), operation,
-                             svn_dirent_local_style(local_abspath,
+                             svn_dirent_local_style(victim_abspath,
                                                     scratch_pool));
 
   option_id = svn_client_conflict_option_get_id(option);
   SVN_ERR_ASSERT(option_id ==
                  svn_client_conflict_option_incoming_move_file_text_merge ||
                  option_id ==
-                 svn_client_conflict_option_incoming_move_dir_merge);
+                 svn_client_conflict_option_both_moved_file_move_merge);
                   
   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
                                              conflict, scratch_pool,
@@ -8338,7 +8549,7 @@ resolve_incoming_move_file_text_merge(sv
             scratch_pool));
 
   /* Set up temporary storage for the common ancestor version of the file. */
-  SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
+  SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath,
                              scratch_pool, scratch_pool));
   SVN_ERR(svn_stream_open_unique(&ancestor_stream,
                                  &ancestor_abspath, wc_tmpdir,
@@ -8368,21 +8579,41 @@ resolve_incoming_move_file_text_merge(sv
                                    details->wc_move_target_idx,
                                    const char *);
 
+  if (local_change == svn_wc_conflict_reason_missing)
+    {
+      /* This is an incoming move vs local move conflict.
+       * Merge from the local move's target location to the
+       * incoming move's target location. */
+      struct conflict_tree_local_missing_details *local_details;
+      apr_array_header_t *moves;
+
+      local_details = conflict->tree_conflict_local_details;
+      moves = svn_hash_gets(local_details->wc_move_targets,
+                            local_details->move_target_repos_relpath);
+      merge_source_abspath =
+        APR_ARRAY_IDX(moves, local_details->wc_move_target_idx, const char *);
+    }
+  else
+    merge_source_abspath = victim_abspath;
+
   /* ### The following WC modifications should be atomic. */
   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
             &lock_abspath, ctx->wc_ctx,
-            svn_dirent_get_longest_ancestor(local_abspath,
+            svn_dirent_get_longest_ancestor(victim_abspath,
                                             moved_to_abspath,
                                             scratch_pool),
             scratch_pool, scratch_pool));
 
-  err = verify_local_state_for_incoming_delete(conflict, option, ctx,
-                                               scratch_pool);
-  if (err)
-    goto unlock_wc;
+  if (local_change != svn_wc_conflict_reason_missing)
+    {
+      err = verify_local_state_for_incoming_delete(conflict, option, ctx,
+                                                   scratch_pool);
+      if (err)
+        goto unlock_wc;
+    }
 
    /* Get a copy of the conflict victim's properties. */
-  err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, local_abspath,
+  err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, merge_source_abspath,
                           scratch_pool, scratch_pool);
   if (err)
     goto unlock_wc;
@@ -8416,7 +8647,8 @@ resolve_incoming_move_file_text_merge(sv
         goto unlock_wc;
 
       err = svn_wc__translated_stream(&working_stream, ctx->wc_ctx,
-                                      local_abspath, local_abspath,
+                                      merge_source_abspath,
+                                      merge_source_abspath,
                                       SVN_WC_TRANSLATE_TO_NF,
                                       scratch_pool, scratch_pool);
       if (err)
@@ -8463,7 +8695,7 @@ resolve_incoming_move_file_text_merge(sv
       err = svn_io_remove_file2(moved_to_abspath, FALSE, scratch_pool);
       if (err)
         goto unlock_wc;
-      err = svn_wc__move2(ctx->wc_ctx, local_abspath, moved_to_abspath,
+      err = svn_wc__move2(ctx->wc_ctx, merge_source_abspath, moved_to_abspath,
                           FALSE, /* ordinary (not meta-data only) move */
                           FALSE, /* mixed-revisions don't apply to files */
                           NULL, NULL, /* don't allow user to cancel here */
@@ -8521,19 +8753,27 @@ resolve_incoming_move_file_text_merge(sv
       operation == svn_wc_operation_switch)
     {
       /* Delete the tree conflict victim (clears the tree conflict marker). */
-      err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
+      err = svn_wc_delete4(ctx->wc_ctx, victim_abspath, FALSE, FALSE,
                            NULL, NULL, /* don't allow user to cancel here */
                            NULL, NULL, /* no extra notification */
                            scratch_pool);
       if (err)
         goto unlock_wc;
     }
+  else if (local_change == svn_wc_conflict_reason_missing)
+    {
+      /* Clear tree conflict marker. */
+      err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath,
+                                      scratch_pool);
+      if (err)
+        goto unlock_wc;
+    }
 
   if (ctx->notify_func2)
     {
       svn_wc_notify_t *notify;
 
-      notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
+      notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
                                     scratch_pool);
       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
     }
@@ -8554,6 +8794,211 @@ unlock_wc:
   return SVN_NO_ERROR;
 }
 
+/* Implements conflict_option_resolve_func_t.
+ * Resolve an incoming move vs local move conflict by merging from the
+ * incoming move's target location to the local move's target location,
+ * overriding the incoming move. */
+static svn_error_t *
+resolve_both_moved_file_text_merge(svn_client_conflict_option_t *option,
+                                   svn_client_conflict_t *conflict,
+                                   svn_client_ctx_t *ctx,
+                                   apr_pool_t *scratch_pool)
+{
+  svn_client_conflict_option_id_t option_id;
+  const char *victim_abspath;
+  const char *local_moved_to_abspath;
+  svn_wc_operation_t operation;
+  const char *lock_abspath;
+  svn_error_t *err;
+  const char *repos_root_url;
+  const char *incoming_old_repos_relpath;
+  svn_revnum_t incoming_old_pegrev;
+  const char *incoming_new_repos_relpath;
+  svn_revnum_t incoming_new_pegrev;
+  const char *wc_tmpdir;
+  const char *ancestor_abspath;
+  svn_stream_t *ancestor_stream;
+  apr_hash_t *ancestor_props;
+  apr_hash_t *incoming_props;
+  apr_hash_t *local_props;
+  const char *ancestor_url;
+  const char *corrected_url;
+  svn_ra_session_t *ra_session;
+  svn_wc_merge_outcome_t merge_content_outcome;
+  svn_wc_notify_state_t merge_props_outcome;
+  apr_array_header_t *propdiffs;
+  struct conflict_tree_incoming_delete_details *incoming_details;
+  apr_array_header_t *possible_moved_to_abspaths;
+  const char *incoming_moved_to_abspath;
+  struct conflict_tree_local_missing_details *local_details;
+  apr_array_header_t *local_moves;
+
+  victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+  operation = svn_client_conflict_get_operation(conflict);
+  incoming_details = conflict->tree_conflict_incoming_details;
+  if (incoming_details == NULL || incoming_details->moves == NULL)
+    return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                             _("The specified conflict resolution option "
+                               "requires details for tree conflict at '%s' "
+                               "to be fetched from the repository first."),
+                            svn_dirent_local_style(victim_abspath,
+                                                   scratch_pool));
+  if (operation == svn_wc_operation_none)
+    return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+                             _("Invalid operation code '%d' recorded for "
+                               "conflict at '%s'"), operation,
+                             svn_dirent_local_style(victim_abspath,
+                                                    scratch_pool));
+
+  option_id = svn_client_conflict_option_get_id(option);
+  SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_file_merge);
+                  
+  SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
+                                             conflict, scratch_pool,
+                                             scratch_pool));
+  SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+            &incoming_old_repos_relpath, &incoming_old_pegrev,
+            NULL, conflict, scratch_pool,
+            scratch_pool));
+  SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+            &incoming_new_repos_relpath, &incoming_new_pegrev,
+            NULL, conflict, scratch_pool,
+            scratch_pool));
+
+  /* Set up temporary storage for the common ancestor version of the file. */
+  SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath,
+                             scratch_pool, scratch_pool));
+  SVN_ERR(svn_stream_open_unique(&ancestor_stream,
+                                 &ancestor_abspath, wc_tmpdir,
+                                 svn_io_file_del_on_pool_cleanup,
+                                 scratch_pool, scratch_pool));
+
+  /* Fetch the ancestor file's content. */
+  ancestor_url = svn_path_url_add_component2(repos_root_url,
+                                             incoming_old_repos_relpath,
+                                             scratch_pool);
+  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
+                                               ancestor_url, NULL, NULL,
+                                               FALSE, FALSE, ctx,
+                                               scratch_pool, scratch_pool));
+  SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev,
+                          ancestor_stream, NULL, /* fetched_rev */
+                          &ancestor_props, scratch_pool));
+  filter_props(ancestor_props, scratch_pool);
+
+  /* Close stream to flush ancestor file to disk. */
+  SVN_ERR(svn_stream_close(ancestor_stream));
+
+  possible_moved_to_abspaths =
+    svn_hash_gets(incoming_details->wc_move_targets,
+                  get_moved_to_repos_relpath(incoming_details, scratch_pool));
+  incoming_moved_to_abspath =
+    APR_ARRAY_IDX(possible_moved_to_abspaths,
+                  incoming_details->wc_move_target_idx, const char *);
+
+  local_details = conflict->tree_conflict_local_details;
+  local_moves = svn_hash_gets(local_details->wc_move_targets,
+                        local_details->move_target_repos_relpath);
+  local_moved_to_abspath =
+    APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *);
+
+  /* ### The following WC modifications should be atomic. */
+  SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
+            &lock_abspath, ctx->wc_ctx,
+            svn_dirent_get_longest_ancestor(victim_abspath,
+                                            local_moved_to_abspath,
+                                            scratch_pool),
+            scratch_pool, scratch_pool));
+
+   /* Get a copy of the incoming moved item's properties. */
+  err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx,
+                          incoming_moved_to_abspath,
+                          scratch_pool, scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  /* Get a copy of the local move target's properties. */
+  err = svn_wc_prop_list2(&local_props, ctx->wc_ctx,
+                          local_moved_to_abspath,
+                          scratch_pool, scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  /* Create a property diff for the files. */
+  err = svn_prop_diffs(&propdiffs, incoming_props, local_props,
+                       scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  /* Perform the file merge. */
+  err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
+                      ctx->wc_ctx, ancestor_abspath,
+                      incoming_moved_to_abspath, local_moved_to_abspath,
+                      NULL, NULL, NULL, /* labels */
+                      NULL, NULL, /* conflict versions */
+                      FALSE, /* dry run */
+                      NULL, NULL, /* diff3_cmd, merge_options */
+                      apr_hash_count(ancestor_props) ? ancestor_props : NULL,
+                      propdiffs,
+                      NULL, NULL, /* conflict func/baton */
+                      NULL, NULL, /* don't allow user to cancel here */
+                      scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  if (ctx->notify_func2)
+    {
+      svn_wc_notify_t *notify;
+
+      /* Tell the world about the file merge that just happened. */
+      notify = svn_wc_create_notify(local_moved_to_abspath,
+                                    svn_wc_notify_update_update,
+                                    scratch_pool);
+      if (merge_content_outcome == svn_wc_merge_conflict)
+        notify->content_state = svn_wc_notify_state_conflicted;
+      else
+        notify->content_state = svn_wc_notify_state_merged;
+      notify->prop_state = merge_props_outcome;
+      notify->kind = svn_node_file;
+      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+    }
+
+  /* Revert local addition of the incoming move's target. */
+  err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath,
+                       svn_depth_infinity, FALSE, NULL, TRUE, FALSE,
+                       FALSE /*added_keep_local*/,
+                       NULL, NULL, /* no cancellation */
+                       ctx->notify_func2, ctx->notify_baton2,
+                       scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  if (ctx->notify_func2)
+    {
+      svn_wc_notify_t *notify;
+
+      notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
+                                    scratch_pool);
+      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+    }
+
+  svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
+
+  conflict->resolution_tree = option_id;
+
+unlock_wc:
+  err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
+                                                                 lock_abspath,
+                                                                 scratch_pool));
+  SVN_ERR(err);
+
+  return SVN_NO_ERROR;
+}
+
 /* Implements conflict_option_resolve_func_t. */
 static svn_error_t *
 resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option,
@@ -8708,8 +9153,9 @@ resolve_incoming_move_dir_merge(svn_clie
       svn_opt_revision_t incoming_new_opt_rev;
 
       /* Revert the incoming move target directory. */
-      SVN_ERR(svn_wc_revert5(ctx->wc_ctx, moved_to_abspath, svn_depth_infinity,
+      SVN_ERR(svn_wc_revert6(ctx->wc_ctx, moved_to_abspath, svn_depth_infinity,
                              FALSE, NULL, TRUE, FALSE,
+                             TRUE /*added_keep_local*/,
                              NULL, NULL, /* no cancellation */
                              ctx->notify_func2, ctx->notify_baton2,
                              scratch_pool));
@@ -8802,7 +9248,9 @@ unlock_wc:
   return SVN_NO_ERROR;
 }
 
-/* Implements conflict_option_resolve_func_t. */
+/* Implements conflict_option_resolve_func_t.
+ * Handles svn_client_conflict_option_local_move_file_text_merge
+ * and svn_client_conflict_option_sibling_move_file_text_merge. */
 static svn_error_t *
 resolve_local_move_file_merge(svn_client_conflict_option_t *option,
                               svn_client_conflict_t *conflict,
@@ -8830,6 +9278,12 @@ resolve_local_move_file_merge(svn_client
   svn_wc_notify_state_t merge_props_outcome;
   apr_array_header_t *propdiffs;
   struct conflict_tree_local_missing_details *details;
+  const char *merge_target_abspath;
+  const char *wcroot_abspath;
+
+  SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+                             conflict->local_abspath, scratch_pool,
+                             scratch_pool));
 
   details = conflict->tree_conflict_local_details;
 
@@ -8845,8 +9299,31 @@ resolve_local_move_file_merge(svn_client
             NULL, conflict, scratch_pool,
             scratch_pool));
 
+  if (details->wc_siblings)
+    {
+      merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings,
+                                           details->preferred_sibling_idx,
+                                           const char *);
+    }
+  else if (details->wc_move_targets && details->move_target_repos_relpath)
+    {
+      apr_array_header_t *moves;
+      moves = svn_hash_gets(details->wc_move_targets,
+                            details->move_target_repos_relpath);
+      merge_target_abspath = APR_ARRAY_IDX(moves, details->wc_move_target_idx,
+                                           const char *);
+    }
+  else

[... 1546 lines stripped ...]