You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by ju...@apache.org on 2022/01/14 14:01:51 UTC

svn commit: r1897034 [15/37] - in /subversion/branches/multi-wc-format: ./ build/ build/ac-macros/ build/generator/ build/generator/swig/ build/generator/templates/ contrib/client-side/ contrib/client-side/svn_load_dirs/ contrib/hook-scripts/ contrib/s...

Modified: subversion/branches/multi-wc-format/subversion/libsvn_client/conflicts.c
URL: http://svn.apache.org/viewvc/subversion/branches/multi-wc-format/subversion/libsvn_client/conflicts.c?rev=1897034&r1=1897033&r2=1897034&view=diff
==============================================================================
--- subversion/branches/multi-wc-format/subversion/libsvn_client/conflicts.c (original)
+++ subversion/branches/multi-wc-format/subversion/libsvn_client/conflicts.c Fri Jan 14 14:01:45 2022
@@ -383,7 +383,7 @@ add_new_move(struct repos_move_info **ne
              const char *author,
              apr_hash_t *moved_paths,
              svn_ra_session_t *ra_session,
-             const char *repos_root_url, 
+             const char *repos_root_url,
              apr_pool_t *result_pool,
              apr_pool_t *scratch_pool)
 {
@@ -661,7 +661,7 @@ match_copies_to_deletion(const char *del
                                           TRUE, iterpool));
               if (!related)
                 continue;
-              
+
               /* Remember details of this move. */
               SVN_ERR(add_new_move(&move, deleted_repos_relpath,
                                    copy->copyto_path, copy->copyfrom_rev,
@@ -669,7 +669,7 @@ match_copies_to_deletion(const char *del
                                    moved_paths, ra_session, repos_root_url,
                                    result_pool, iterpool));
               push_move(move, moves_table, result_pool);
-            } 
+            }
         }
       else
         {
@@ -782,7 +782,7 @@ map_deleted_path_to_move(const char *del
     {
       const char *relpath;
       struct repos_move_info *move;
-          
+
       move = APR_ARRAY_IDX(moves, i, struct repos_move_info *);
       if (strcmp(move->moved_from_repos_relpath, deleted_relpath) == 0)
         return move;
@@ -806,7 +806,7 @@ map_deleted_path_to_move(const char *del
   if (closest_move)
     {
       const char *relpath;
-      
+
       /* 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);
@@ -967,7 +967,7 @@ cache_copied_item(apr_hash_t *copies, co
  * This function answers the same question as svn_ra_get_deleted_rev() but
  * works in cases where we do not already know a revision in which the deleted
  * node once used to exist.
- * 
+ *
  * If the node was moved, rather than deleted, return move information
  * in BATON->MOVE.
  */
@@ -1098,7 +1098,7 @@ find_deleted_rev(void *baton,
         b->deleted_rev_author = apr_pstrdup(b->result_pool, author->data);
       else
         b->deleted_rev_author = _("unknown author");
-          
+
       b->replacing_node_kind = replacing_node_kind;
 
       /* We're done. Abort the log operation. */
@@ -1173,7 +1173,7 @@ describe_local_file_node_change(const ch
           const char *moved_to_abspath;
           svn_error_t *err;
 
-          err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL, 
+          err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
                                             ctx->wc_ctx,
                                             conflict->local_abspath,
                                             scratch_pool,
@@ -1257,7 +1257,7 @@ describe_local_file_node_change(const ch
         {
           const char *moved_from_abspath;
 
-          SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL, 
+          SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
                                               ctx->wc_ctx,
                                               conflict->local_abspath,
                                               scratch_pool,
@@ -1398,7 +1398,7 @@ describe_local_dir_node_change(const cha
           const char *moved_to_abspath;
           svn_error_t *err;
 
-          err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL, 
+          err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
                                             ctx->wc_ctx,
                                             conflict->local_abspath,
                                             scratch_pool,
@@ -1483,7 +1483,7 @@ describe_local_dir_node_change(const cha
         {
           const char *moved_from_abspath;
 
-          SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL, 
+          SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
                                               ctx->wc_ctx,
                                               conflict->local_abspath,
                                               scratch_pool,
@@ -1581,7 +1581,7 @@ struct find_moves_baton
    *   rB: mv b->c
    *   rC: mv c->d
    * we map each revision number to all the moves which happened in the
-   * revision, which looks as follows: 
+   * revision, which looks as follows:
    *   rA : [(x->z), (a->b)]
    *   rB : [(b->c)]
    *   rC : [(c->d)]
@@ -1700,7 +1700,7 @@ find_moves(void *baton, svn_log_entry_t
   return SVN_NO_ERROR;
 }
 
-/* Find all moves which occured in repository history starting at
+/* Find all moves which occurred in repository history starting at
  * REPOS_RELPATH@START_REV until END_REV (where START_REV > END_REV).
  * Return results in *MOVES_TABLE (see struct find_moves_baton for details). */
 static svn_error_t *
@@ -2628,7 +2628,7 @@ collect_sibling_move_candidates(apr_arra
                                 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;
@@ -2675,91 +2675,88 @@ follow_move_chains(apr_hash_t *wc_move_t
                    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);
+  apr_array_header_t *candidate_abspaths;
 
-          moved_to_abspaths = apr_array_make(result_pool, 1,
-                                             sizeof (const char *));
+  /* 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));
 
-          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;
+  if (candidate_abspaths->nelts > 0)
+    {
+      apr_array_header_t *moved_to_abspaths;
+      int i;
+      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
 
-              svn_pool_clear(iterpool);
+      moved_to_abspaths = apr_array_make(result_pool, 1,
+                                         sizeof (const char *));
 
-              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));
+      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;
 
-              if (candidate_revision == SVN_INVALID_REVNUM)
-                continue;
+          svn_pool_clear(iterpool);
 
-              /* 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;
+          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)
+              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)
                     {
-                      if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
-                        {
-                          svn_error_clear(err);
-                          yca_loc = NULL;
-                        }
-                      else
-                        return svn_error_trace(err);
+                      svn_error_clear(err);
+                      yca_loc = NULL;
                     }
-
-                  if (yca_loc == NULL)
-                    continue;
+                  else
+                    return svn_error_trace(err);
                 }
 
-              APR_ARRAY_PUSH(moved_to_abspaths, const char *) =
-                apr_pstrdup(result_pool, candidate_abspath);
+              if (yca_loc == NULL)
+                continue;
             }
-          svn_pool_destroy(iterpool);
 
-          svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath,
-                        moved_to_abspaths);
+          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
+
+  if (move->next)
     {
       int i;
       apr_pool_t *iterpool;
@@ -2777,7 +2774,7 @@ follow_move_chains(apr_hash_t *wc_move_t
                                      ctx, victim_abspath, victim_node_kind,
                                      victim_repos_relpath, victim_revision,
                                      result_pool, iterpool));
-                                        
+
         }
       svn_pool_destroy(iterpool);
     }
@@ -2843,19 +2840,33 @@ conflict_tree_get_details_local_missing(
 
   /* Pick the younger incoming node as our 'related node' which helps
    * pin-pointing the deleted conflict victim in history. */
-  related_repos_relpath = 
+  related_repos_relpath =
             (old_rev < new_rev ? new_repos_relpath : old_repos_relpath);
   related_peg_rev = (old_rev < new_rev ? new_rev : old_rev);
 
   /* Make sure we're going to search the related node in a revision where
    * it exists. The younger incoming node might have been deleted in HEAD. */
   if (related_repos_relpath != NULL && related_peg_rev != SVN_INVALID_REVNUM)
-    SVN_ERR(find_related_node(
-              &related_repos_relpath, &related_peg_rev,
-              related_repos_relpath, related_peg_rev,
-              (old_rev < new_rev ? old_repos_relpath : new_repos_relpath),
-              (old_rev < new_rev ? old_rev : new_rev),
-              conflict, ctx, scratch_pool, scratch_pool));
+    {
+      const char *older_related_repos_relpath;
+      svn_revnum_t older_related_peg_rev;
+      SVN_ERR(find_related_node(
+                &older_related_repos_relpath, &older_related_peg_rev,
+                related_repos_relpath, related_peg_rev,
+                (old_rev < new_rev ? old_repos_relpath : new_repos_relpath),
+                (old_rev < new_rev ? old_rev : new_rev),
+                conflict, ctx, scratch_pool, scratch_pool));
+      if (older_related_repos_relpath != NULL &&
+          older_related_peg_rev != SVN_INVALID_REVNUM)
+        {
+          related_repos_relpath = older_related_repos_relpath;
+          related_peg_rev = older_related_peg_rev;
+        }
+    }
+
+  /* Bail if we are unable to find the related node. */
+  if (related_repos_relpath == NULL || related_peg_rev == SVN_INVALID_REVNUM)
+    return SVN_NO_ERROR;
 
   /* Set END_REV to our best guess of the nearest YCA revision. */
   url = svn_path_url_add_component2(repos_root_url, related_repos_relpath,
@@ -3015,14 +3026,14 @@ conflict_tree_get_details_local_missing(
   if (deleted_rev != SVN_INVALID_REVNUM)
     details->deleted_repos_relpath = svn_relpath_join(parent_repos_relpath,
                                                       deleted_basename,
-                                                      conflict->pool); 
+                                                      conflict->pool);
   details->moves = moves;
+  details->wc_move_targets = apr_hash_make(conflict->pool);
   if (details->moves != NULL)
     {
       apr_pool_t *iterpool;
       int i;
 
-      details->wc_move_targets = apr_hash_make(conflict->pool);
       iterpool = svn_pool_create(scratch_pool);
       for (i = 0; i < details->moves->nelts; i++)
         {
@@ -3061,7 +3072,7 @@ conflict_tree_get_details_local_missing(
           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)
@@ -3247,7 +3258,7 @@ conflict_tree_get_description_local_miss
   if (details->moves || details->sibling_moves)
     {
       struct repos_move_info *move;
-      
+
       *description = _("No such file or directory was found in the "
                        "merge target working copy.\n");
 
@@ -3927,7 +3938,7 @@ describe_incoming_deletion_upon_update(
               struct repos_move_info *move;
 
               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
-              description = 
+              description =
                 apr_psprintf(result_pool,
                              _("Item updated from r%ld to r%ld was moved "
                                "to '^/%s' by %s in r%ld."), old_rev, new_rev,
@@ -4065,7 +4076,7 @@ describe_incoming_deletion_upon_switch(
                                                        result_pool,
                                                        scratch_pool);
             }
-          return description;    
+          return description;
         }
       else if (victim_node_kind == svn_node_file ||
                victim_node_kind == svn_node_symlink)
@@ -4218,7 +4229,7 @@ describe_incoming_deletion_upon_switch(
             {
               struct repos_move_info *move;
               const char *description;
-              
+
               move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
               description =
                 apr_psprintf(result_pool,
@@ -4849,7 +4860,7 @@ conflict_tree_get_description_incoming_d
           action = describe_incoming_reverse_addition_upon_switch(
                      details, victim_node_kind, old_repos_relpath, old_rev,
                      new_repos_relpath, new_rev, result_pool);
-            
+
         }
       }
   else if (conflict_operation == svn_wc_operation_merge)
@@ -4894,7 +4905,7 @@ struct find_added_rev_baton
  * Finds the revision in which a node was added by tracing 'start'
  * revisions in location segments reported for the node.
  * If the PARENT_REPOS_RELPATH in the baton is not NULL, only consider
- * segments in which the node existed somwhere beneath this path. */
+ * segments in which the node existed somewhere beneath this path. */
 static svn_error_t *
 find_added_rev(svn_location_segment_t *segment,
                void *baton,
@@ -5361,7 +5372,7 @@ conflict_tree_get_details_incoming_add(s
       details->deleted_rev_author = NULL;
 
       /* Figure out whether this node was deleted later.
-       * ### Could probably optimize by infering both addition and deletion
+       * ### Could probably optimize by inferring both addition and deletion
        * ### from svn_ra_get_location_segments() call above. */
       SVN_ERR(svn_ra_get_latest_revnum(ra_session, &head_rev, scratch_pool));
       if (new_rev < head_rev)
@@ -5835,10 +5846,10 @@ conflict_tree_get_description_incoming_a
 
 /* Details for tree conflicts involving incoming edits.
  * Note that we store an array of these. Each element corresponds to a
- * revision within the old/new range in which a modification occured. */
+ * revision within the old/new range in which a modification occurred. */
 struct conflict_tree_incoming_edit_details
 {
-  /* The revision in which the edit ocurred. */
+  /* The revision in which the edit occurred. */
   svn_revnum_t rev;
 
   /* The author of the revision. */
@@ -6143,6 +6154,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
@@ -6190,7 +6204,7 @@ describe_incoming_edit_list_modified_rev
                                details->rev, details->author,
                                i < edits->nelts - 1 ? "," : "");
             }
-        } 
+        }
       else
         s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
                          details->rev, details->author,
@@ -6339,7 +6353,7 @@ conflict_tree_get_description_incoming_e
                                         "during reverse-merge of\n"
                                         "'^/%s:%ld-%ld'"),
                                       new_repos_relpath, new_rev + 1, old_rev);
-                
+
               else
                 action = apr_psprintf(scratch_pool,
                                       _("Changes from the following revisions "
@@ -6374,7 +6388,7 @@ svn_client_conflict_tree_get_description
   SVN_ERR(conflict->tree_conflict_get_local_description_func(
             local_change_description,
             conflict, ctx, result_pool, scratch_pool));
-  
+
   return SVN_NO_ERROR;
 }
 
@@ -6898,10 +6912,12 @@ 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. */
+  /* Set up temporary storage for the working version of file. */
   SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
                              scratch_pool, scratch_pool));
   SVN_ERR(svn_stream_open_unique(&working_file_tmp_stream,
@@ -6910,20 +6926,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,
@@ -6976,7 +7003,7 @@ unlock_wc:
                                                             scratch_pool));
   svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
   SVN_ERR(err);
-  
+
   if (ctx->notify_func2)
     {
       svn_wc_notify_t *notify;
@@ -7169,7 +7196,7 @@ resolve_merge_incoming_added_file_replac
 
   local_abspath = svn_client_conflict_get_local_abspath(conflict);
 
-  /* Set up tempory storage for the working version of file. */
+  /* Set up temporary storage for the working version of file. */
   SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
                              scratch_pool, scratch_pool));
   SVN_ERR(svn_stream_open_unique(&working_file_tmp_stream,
@@ -7854,7 +7881,7 @@ resolve_merge_incoming_added_dir_merge(s
       if (details->added_rev == SVN_INVALID_REVNUM)
         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
                                  _("Could not determine when '%s' was "
-                                   "added the repository"),
+                                   "added to the repository"),
                                  svn_dirent_local_style(local_abspath,
                                                         scratch_pool));
       rev1 = rev_below(details->added_rev);
@@ -7922,20 +7949,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,
@@ -7945,35 +7999,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. */
@@ -7992,14 +8017,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);
 
@@ -8020,47 +8039,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 */
-                                      NULL, /* default WC format */
-                                      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,
@@ -8077,31 +8055,11 @@ 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_by_editor(&timestamp_sleep,
+                                               svn_node_dir,
+                                               url, incoming_new_pegrev,
+                                               local_abspath,
+                                               ra_session, ctx, scratch_pool);
   if (err)
     goto unlock_wc;
 
@@ -8246,7 +8204,7 @@ ensure_local_edit_vs_incoming_deletion_c
 
   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,
@@ -8538,7 +8496,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;
@@ -8564,7 +8524,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)
@@ -8572,21 +8533,21 @@ 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,
                                              scratch_pool));
@@ -8600,7 +8561,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,
@@ -8630,21 +8591,48 @@ 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;
+
+      local_details = conflict->tree_conflict_local_details;
+      if (local_details->wc_move_targets &&
+          local_details->move_target_repos_relpath)
+        {
+          apr_array_header_t *moves;
+          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;
+    }
+  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;
@@ -8665,10 +8653,10 @@ resolve_incoming_move_file_text_merge(sv
   if (operation == svn_wc_operation_update ||
       operation == svn_wc_operation_switch)
     {
-      svn_stream_t *working_stream;
+      svn_stream_t *moved_to_stream;
       svn_stream_t *incoming_stream;
 
-      /* Create a temporary copy of the working file in repository-normal form.
+      /* Create a temporary copy of the moved file in repository-normal form.
        * Set up this temporary file to be automatically removed. */
       err = svn_stream_open_unique(&incoming_stream,
                                    &incoming_abspath, wc_tmpdir,
@@ -8677,18 +8665,31 @@ resolve_incoming_move_file_text_merge(sv
       if (err)
         goto unlock_wc;
 
-      err = svn_wc__translated_stream(&working_stream, ctx->wc_ctx,
-                                      local_abspath, local_abspath,
+      err = svn_wc__translated_stream(&moved_to_stream, ctx->wc_ctx,
+                                      moved_to_abspath,
+                                      moved_to_abspath,
                                       SVN_WC_TRANSLATE_TO_NF,
                                       scratch_pool, scratch_pool);
       if (err)
         goto unlock_wc;
 
-      err = svn_stream_copy3(working_stream, incoming_stream,
+      err = svn_stream_copy3(moved_to_stream, incoming_stream,
                              NULL, NULL, /* no cancellation */
                              scratch_pool);
       if (err)
         goto unlock_wc;
+
+      /* Overwrite the moved file with the conflict victim's content.
+       * Incoming changes will be merged in from the temporary file created
+       * above. This is required to correctly make local changes show up as
+       * 'mine' during the three-way text merge between the ancestor file,
+       * the conflict victim ('mine'), and the moved file ('theirs') which
+       * was brought in by the update/switch operation and occupies the path
+       * of the merge target. */
+      err = svn_io_copy_file(merge_source_abspath, moved_to_abspath, FALSE,
+                             scratch_pool);
+      if (err)
+        goto unlock_wc;
     }
   else if (operation == svn_wc_operation_merge)
     {
@@ -8725,7 +8726,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 */
@@ -8761,7 +8762,7 @@ resolve_incoming_move_file_text_merge(sv
         goto unlock_wc;
       incoming_abspath = NULL;
     }
-  
+
   if (ctx->notify_func2)
     {
       svn_wc_notify_t *notify;
@@ -8783,19 +8784,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);
     }
@@ -8816,6 +8825,521 @@ 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.
+ * Resolve an incoming move vs local move conflict by moving the locally moved
+ * directory to the incoming move target location, and then merging changes. */
+static svn_error_t *
+resolve_both_moved_dir_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 *incoming_moved_repos_relpath;
+  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;
+  svn_client__conflict_report_t *conflict_report;
+  const char *incoming_old_url;
+  const char *incoming_moved_url;
+  svn_opt_revision_t incoming_old_opt_rev;
+  svn_opt_revision_t incoming_moved_opt_rev;
+
+  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_dir_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));
+
+  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));
+
+  /* Perform the merge. */
+  incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
+                                 incoming_old_repos_relpath, SVN_VA_NULL);
+  incoming_old_opt_rev.kind = svn_opt_revision_number;
+  incoming_old_opt_rev.value.number = incoming_old_pegrev;
+
+  incoming_moved_repos_relpath =
+      get_moved_to_repos_relpath(incoming_details, scratch_pool);
+  incoming_moved_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
+                                   incoming_moved_repos_relpath, SVN_VA_NULL);
+  incoming_moved_opt_rev.kind = svn_opt_revision_number;
+  incoming_moved_opt_rev.value.number = incoming_new_pegrev;
+  err = svn_client__merge_locked(&conflict_report,
+                                 incoming_old_url, &incoming_old_opt_rev,
+                                 incoming_moved_url, &incoming_moved_opt_rev,
+                                 local_moved_to_abspath, svn_depth_infinity,
+                                 TRUE, TRUE, /* do a no-ancestry merge */
+                                 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;
+
+  /* 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.
+ * 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_dir_move_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;
+  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;
+  svn_client__conflict_report_t *conflict_report;
+  const char *incoming_old_url;
+  const char *incoming_moved_url;
+  svn_opt_revision_t incoming_old_opt_rev;
+  svn_opt_revision_t incoming_moved_opt_rev;
+
+  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_dir_move_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));
+
+  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));
+
+  /* Revert the incoming move target directory. */
+  err = svn_wc_revert6(ctx->wc_ctx, incoming_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);
+  if (err)
+    goto unlock_wc;
+
+  /* The move operation is not part of natural history. We must replicate
+   * this move in our history. Record a move in the working copy. */
+  err = svn_wc__move2(ctx->wc_ctx, local_moved_to_abspath,
+                      incoming_moved_to_abspath,
+                      FALSE, /* this is not a meta-data only move */
+                      TRUE, /* allow mixed-revisions just in case */
+                      NULL, NULL, /* don't allow user to cancel here */
+                      ctx->notify_func2, ctx->notify_baton2,
+                      scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  /* Merge INCOMING_OLD_URL@MERGE_LEFT->INCOMING_MOVED_URL@MERGE_RIGHT
+   * into the locally moved merge target. */
+  incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
+                                 incoming_old_repos_relpath, SVN_VA_NULL);
+  incoming_old_opt_rev.kind = svn_opt_revision_number;
+  incoming_old_opt_rev.value.number = incoming_old_pegrev;
+
+  incoming_moved_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
+                                   incoming_details->move_target_repos_relpath,
+                                   SVN_VA_NULL);
+  incoming_moved_opt_rev.kind = svn_opt_revision_number;
+  incoming_moved_opt_rev.value.number = incoming_new_pegrev;
+  err = svn_client__merge_locked(&conflict_report,
+                                 incoming_old_url, &incoming_old_opt_rev,
+                                 incoming_moved_url, &incoming_moved_opt_rev,
+                                 incoming_moved_to_abspath, svn_depth_infinity,
+                                 TRUE, TRUE, /* do a no-ancestry merge */
+                                 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;
+
+  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,
@@ -8841,8 +9365,8 @@ resolve_incoming_move_dir_merge(svn_clie
   struct conflict_tree_incoming_delete_details *details;
   apr_array_header_t *possible_moved_to_abspaths;
   const char *moved_to_abspath;
-  svn_client__pathrev_t *yca_loc;
-  svn_opt_revision_t yca_opt_rev;
+  const char *incoming_old_url;
+  svn_opt_revision_t incoming_old_opt_rev;
   svn_client__conflict_report_t *conflict_report;
   svn_boolean_t is_copy;
   svn_boolean_t is_modified;
@@ -8861,7 +9385,7 @@ resolve_incoming_move_dir_merge(svn_clie
   option_id = svn_client_conflict_option_get_id(option);
   SVN_ERR_ASSERT(option_id ==
                  svn_client_conflict_option_incoming_move_dir_merge);
-                  
+
   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid,
                                              conflict, scratch_pool,
                                              scratch_pool));
@@ -8935,30 +9459,6 @@ resolve_incoming_move_dir_merge(svn_clie
       goto unlock_wc;
     }
 
-  /* Now find the youngest common ancestor of these nodes. */
-  err = find_yca(&yca_loc, victim_repos_relpath, victim_peg_rev,
-                 moved_to_repos_relpath, moved_to_peg_rev,
-                 repos_root_url, repos_uuid,
-                 NULL, ctx, scratch_pool, scratch_pool);
-  if (err)
-    goto unlock_wc;
-
-  if (yca_loc == NULL)
-    {
-      err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                              _("Cannot resolve tree conflict on '%s' "
-                                "(could not find common ancestor of '^/%s@%ld' "
-                                " and '^/%s@%ld')"),
-                              svn_dirent_local_style(local_abspath,
-                                                     scratch_pool),
-                              victim_repos_relpath, victim_peg_rev,
-                              moved_to_repos_relpath, moved_to_peg_rev);
-      goto unlock_wc;
-    }
-
-  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, ctx,
                                                scratch_pool);
   if (err)
@@ -8970,12 +9470,14 @@ 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_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));
+      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);
+      if (err)
+        goto unlock_wc;
 
       /* The move operation is not part of natural history. We must replicate
        * this move in our history. Record a move in the working copy. */
@@ -8988,7 +9490,12 @@ resolve_incoming_move_dir_merge(svn_clie
       if (err)
         goto unlock_wc;
 
-      /* Merge YCA_URL@YCA_REV->MOVE_TARGET_URL@MERGE_RIGHT into move target. */
+      /* Merge INCOMING_OLD_URL@MERGE_LEFT->MOVE_TARGET_URL@MERGE_RIGHT
+       * into move target. */
+      incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
+                                     incoming_old_repos_relpath, SVN_VA_NULL);
+      incoming_old_opt_rev.kind = svn_opt_revision_number;
+      incoming_old_opt_rev.value.number = incoming_old_pegrev;
       move_target_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
                                     get_moved_to_repos_relpath(details,
                                                                scratch_pool),
@@ -8996,7 +9503,7 @@ resolve_incoming_move_dir_merge(svn_clie
       incoming_new_opt_rev.kind = svn_opt_revision_number;
       incoming_new_opt_rev.value.number = incoming_new_pegrev;
       err = svn_client__merge_locked(&conflict_report,
-                                     yca_loc->url, &yca_opt_rev,
+                                     incoming_old_url, &incoming_old_opt_rev,
                                      move_target_url, &incoming_new_opt_rev,
                                      moved_to_abspath, svn_depth_infinity,
                                      TRUE, TRUE, /* do a no-ancestry merge */
@@ -9283,7 +9790,7 @@ resolve_local_move_dir_merge(svn_client_
             NULL, conflict, scratch_pool,
             scratch_pool));
 
-  if (details->wc_move_targets)
+  if (details->wc_move_targets && details->move_target_repos_relpath)
     {
       apr_array_header_t *moves;
 
@@ -9784,6 +10291,7 @@ configure_option_incoming_added_file_tex
       incoming_new_kind == svn_node_file &&
       incoming_change == svn_wc_conflict_action_add &&
       (local_change == svn_wc_conflict_reason_obstructed ||
+       local_change == svn_wc_conflict_reason_unversioned ||
        local_change == svn_wc_conflict_reason_added))
     {
       const char *description;
@@ -9909,8 +10417,9 @@ configure_option_incoming_added_dir_merg
       incoming_change == svn_wc_conflict_action_add &&
       (local_change == svn_wc_conflict_reason_added ||
        (operation == svn_wc_operation_merge &&
-       local_change == svn_wc_conflict_reason_obstructed)))
-
+        local_change == svn_wc_conflict_reason_obstructed) ||
+       (operation != svn_wc_operation_merge &&
+        local_change == svn_wc_conflict_reason_unversioned)))
     {
       const char *description;
       const char *wcroot_abspath;
@@ -10120,7 +10629,7 @@ configure_option_incoming_delete_ignore(
           is_local_move = (local_details != NULL &&
                            local_details->moves != NULL);
 
-          if (!is_incoming_move && !is_local_move)
+          if (is_incoming_move || is_local_move)
             return SVN_NO_ERROR;
         }
 
@@ -10166,7 +10675,8 @@ configure_option_incoming_delete_accept(
                           incoming_details->moves != NULL);
       if (is_incoming_move &&
           (local_change == svn_wc_conflict_reason_edited ||
-          local_change == svn_wc_conflict_reason_moved_away))
+          local_change == svn_wc_conflict_reason_moved_away ||
+          local_change == svn_wc_conflict_reason_missing))
         {
           /* An option which accepts the incoming deletion makes no sense
            * if we know there was a local move and/or an incoming move. */
@@ -10203,39 +10713,76 @@ describe_incoming_move_merge_conflict_op
   const char **description,
   svn_client_conflict_t *conflict,
   svn_client_ctx_t *ctx,
-  struct conflict_tree_incoming_delete_details *details,
+  const char *moved_to_abspath,
   apr_pool_t *result_pool,
   apr_pool_t *scratch_pool)
 {
-  apr_array_header_t *move_target_wc_abspaths;
   svn_wc_operation_t operation;
   const char *victim_abspath;
-  const char *moved_to_abspath;
+  svn_node_kind_t victim_node_kind;
   const char *wcroot_abspath;
 
-  move_target_wc_abspaths =
-    svn_hash_gets(details->wc_move_targets,
-                  get_moved_to_repos_relpath(details, scratch_pool));
-  moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
-                                   details->wc_move_target_idx,
-                                   const char *);
-
   victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+  victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
   SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
                              victim_abspath, scratch_pool,
                              scratch_pool));
 
   operation = svn_client_conflict_get_operation(conflict);
   if (operation == svn_wc_operation_merge)
-    *description =
-      apr_psprintf(
-        result_pool, _("move '%s' to '%s' and merge"),
-        svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
-                                                        victim_abspath),
-                               scratch_pool),
-        svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
-                                                        moved_to_abspath),
-                               scratch_pool));
+    {
+      const char *incoming_moved_abspath = NULL;
+
+      if (victim_node_kind == svn_node_none)
+        {
+          /* This is an incoming move vs local move conflict. */
+          struct conflict_tree_incoming_delete_details *details;
+
+          details = conflict->tree_conflict_incoming_details;
+          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);
+              incoming_moved_abspath =
+                APR_ARRAY_IDX(moves, details->wc_move_target_idx,
+                              const char *);
+            }
+        }
+
+      if (incoming_moved_abspath)
+        {
+          /* The 'move and merge' option follows the incoming move; note that
+           * moved_to_abspath points to the current location of an item which
+           * was moved in the history of our merge target branch. If the user
+           * chooses 'move and merge', that item will be moved again (i.e. it
+           * will be moved to and merged with incoming_moved_abspath's item). */
+          *description =
+            apr_psprintf(
+              result_pool, _("move '%s' to '%s' and merge"),
+              svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
+                                                              moved_to_abspath),
+                                     scratch_pool),
+              svn_dirent_local_style(svn_dirent_skip_ancestor(
+                                       wcroot_abspath,
+                                       incoming_moved_abspath),
+                                     scratch_pool));
+        }
+      else
+        {
+          *description =
+            apr_psprintf(
+              result_pool, _("move '%s' to '%s' and merge"),
+              svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
+                                                              victim_abspath),
+                                     scratch_pool),
+              svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
+                                                              moved_to_abspath),
+                                     scratch_pool));
+        }
+    }
   else
     *description =
       apr_psprintf(
@@ -10260,13 +10807,16 @@ configure_option_incoming_move_file_merg
 {
   svn_node_kind_t victim_node_kind;
   svn_wc_conflict_action_t incoming_change;
+  svn_wc_conflict_reason_t local_change;
   const char *incoming_old_repos_relpath;
   svn_revnum_t incoming_old_pegrev;
   svn_node_kind_t incoming_old_kind;
   const char *incoming_new_repos_relpath;
   svn_revnum_t incoming_new_pegrev;
   svn_node_kind_t incoming_new_kind;
+
   incoming_change = svn_client_conflict_get_incoming_change(conflict);
+  local_change = svn_client_conflict_get_local_change(conflict);
   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
             &incoming_old_repos_relpath, &incoming_old_pegrev,
@@ -10280,10 +10830,13 @@ configure_option_incoming_move_file_merg
   if (victim_node_kind == svn_node_file &&
       incoming_old_kind == svn_node_file &&
       incoming_new_kind == svn_node_none &&
-      incoming_change == svn_wc_conflict_action_delete)
+      incoming_change == svn_wc_conflict_action_delete &&
+      local_change == svn_wc_conflict_reason_edited)
     {
       struct conflict_tree_incoming_delete_details *details;
       const char *description;
+      apr_array_header_t *move_target_wc_abspaths;
+      const char *moved_to_abspath;
 
       details = conflict->tree_conflict_incoming_details;
       if (details == NULL || details->moves == NULL)
@@ -10292,9 +10845,15 @@ configure_option_incoming_move_file_merg
       if (apr_hash_count(details->wc_move_targets) == 0)
         return SVN_NO_ERROR;
 
+      move_target_wc_abspaths =
+        svn_hash_gets(details->wc_move_targets,
+                      get_moved_to_repos_relpath(details, scratch_pool));
+      moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
+                                       details->wc_move_target_idx,
+                                       const char *);
       SVN_ERR(describe_incoming_move_merge_conflict_option(&description,
                                                            conflict, ctx,
-                                                           details,
+                                                           moved_to_abspath,
                                                            scratch_pool,
                                                            scratch_pool));
       add_resolution_option(
@@ -10345,6 +10904,8 @@ configure_option_incoming_dir_merge(svn_
     {
       struct conflict_tree_incoming_delete_details *details;
       const char *description;
+      apr_array_header_t *move_target_wc_abspaths;
+      const char *moved_to_abspath;
 
       details = conflict->tree_conflict_incoming_details;
       if (details == NULL || details->moves == NULL)
@@ -10353,9 +10914,15 @@ configure_option_incoming_dir_merge(svn_
       if (apr_hash_count(details->wc_move_targets) == 0)
         return SVN_NO_ERROR;
 
+      move_target_wc_abspaths =
+        svn_hash_gets(details->wc_move_targets,
+                      get_moved_to_repos_relpath(details, scratch_pool));
+      moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
+                                       details->wc_move_target_idx,
+                                       const char *);
       SVN_ERR(describe_incoming_move_merge_conflict_option(&description,
                                                            conflict, ctx,
-                                                           details,
+                                                           moved_to_abspath,
                                                            scratch_pool,
                                                            scratch_pool));
       add_resolution_option(options, conflict,
@@ -10538,6 +11105,828 @@ configure_option_sibling_move_merge(svn_
   return SVN_NO_ERROR;
 }
 
+struct conflict_tree_update_local_moved_away_details {
+  /*
+   * This array consists of "const char *" absolute paths to working copy
+   * nodes which are uncommitted copies and correspond to the repository path
+   * of the conflict victim.
+   * 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 array in case if there is no move target path in
+   * the working copy. */
+  apr_array_header_t *wc_move_targets;
+
+  /* Current index into the list of working copy paths in WC_MOVE_TARGETS. */
+  int preferred_move_target_idx;
+};
+
+/* 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. The original local move was broken during
+ * update/switch, so overriding the incoming move involves recording a new
+ * move from the incoming move's target location to the local move's target
+ * location. */
+static svn_error_t *
+resolve_both_moved_file_update_keep_local_move(
+  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_update_local_moved_away_details *local_details;
+
+  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_moved_to_abspath =
+    APR_ARRAY_IDX(local_details->wc_move_targets,
+                  local_details->preferred_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;
+

[... 957 lines stripped ...]