You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2018/09/16 10:43:17 UTC

svn commit: r1840995 - in /subversion/trunk/subversion: libsvn_client/conflicts.c svn/conflict-callbacks.c tests/libsvn_client/conflicts-test.c

Author: stsp
Date: Sun Sep 16 10:43:17 2018
New Revision: 1840995

URL: http://svn.apache.org/viewvc?rev=1840995&view=rev
Log:
Implement support for ambiguous moves for 'local missing' conflicts.

* subversion/libsvn_client/conflicts.c
  (conflict_tree_local_missing_details): Replace the single 'moved_to_abspath'
   with appropriate data structures for supporting ambiguous moves: A map
   of repository-side move targets and corresponding working copy paths.
   The new data structures are similar to those used for 'incoming delete'
   style conflicts.
  (follow_move_chains): Move upwards in the file so it can be used earlier.
  (conflict_tree_get_details_local_missing): Initialize the new map of
   repository-side move targets. Again, this is similar to what the
   corresponding 'get details' function for 'incoming delete' conflicts does.
  (resolve_local_move_file_merge): Replace use of 'merge_target_abspath' with
   use of the new move target map.
  (configure_option_local_move_file_merge): Stop initializing move information
   in conflict details here. This is now done earlier, when the details are
   actually fetched from the repository. Doing this here is the wrong place
   for the task, since we would clobber move target selections the user may
   have already made. And again, the 'incoming delete' case works similar to
   this new implementation for 'local missing'.
  (get_repos_relpath_candidates): New helper function factored out from...
  (svn_client_conflict_option_get_moved_to_repos_relpath_candidates): ...here.
   Support selection of repository move targets with the new 'local missing'
   details data structures.
  (svn_client_conflict_option_set_moved_to_repos_relpath: Add support for
   'local missing' conflicts to this function.
  (set_wc_move_target): New helper function, factored out from...
  (svn_client_conflict_option_get_moved_to_abspath_candidates): ...here.
   Support the new move data structures used for 'local missing' conflicts.

* subversion/svn/conflict-callbacks.c
  (build_tree_conflict_options): Obtain the list of working copy move targets
   for 'local missing' options as well as 'incoming delete' style options.
  (handle_tree_conflict): Allow choosing ambiguous move targets for
    'local missing' style options.

* subversion/tests/libsvn_client/conflicts-test.c
  (test_local_missing_abiguous_moves, test_funcs): New test.

Modified:
    subversion/trunk/subversion/libsvn_client/conflicts.c
    subversion/trunk/subversion/svn/conflict-callbacks.c
    subversion/trunk/subversion/tests/libsvn_client/conflicts-test.c

Modified: subversion/trunk/subversion/libsvn_client/conflicts.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/conflicts.c?rev=1840995&r1=1840994&r2=1840995&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/conflicts.c (original)
+++ subversion/trunk/subversion/libsvn_client/conflicts.c Sun Sep 16 10:43:17 2018
@@ -2350,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;
 
@@ -2357,17 +2359,38 @@ 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 not NULL, this is the move target abspath. */
-  const char *moved_to_abspath;
+  /* 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. */
@@ -2635,6 +2658,133 @@ collect_sibling_move_candidates(apr_arra
   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;
+}
+
 /* Implements tree_conflict_get_details_func_t. */
 static svn_error_t *
 conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
@@ -2862,6 +3012,51 @@ conflict_tree_get_details_local_missing(
                                                       deleted_basename,
                                                       conflict->pool); 
   details->moves = moves;
+  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++)
+        {
+          struct repos_move_info *move;
+
+          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,
+                                     svn_node_file,
+                                     new_repos_relpath,
+                                     new_rev,
+                                     scratch_pool, iterpool));
+        }
+      svn_pool_destroy(iterpool);
+
+      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_siblings && details->wc_siblings->nelts == 1)
@@ -2875,7 +3070,7 @@ conflict_tree_get_details_local_missing(
           conflict->recommended_option_id =
             svn_client_conflict_option_sibling_move_dir_merge;
     }
-                                         
+
   conflict->tree_conflict_local_details = details;
 
   return SVN_NO_ERROR;
@@ -4756,156 +4951,29 @@ get_incoming_delete_details_for_reverse_
                                        scratch_pool));
 
   SVN_ERR(svn_ra_rev_prop(ra_session, b.added_rev,
-                          SVN_PROP_REVISION_AUTHOR,
-                          &author_revprop, scratch_pool));
-  (*details)->deleted_rev = SVN_INVALID_REVNUM;
-  (*details)->added_rev = b.added_rev;
-  (*details)->repos_relpath = apr_pstrdup(result_pool, b.repos_relpath);
-  if (author_revprop)
-    (*details)->rev_author = apr_pstrdup(result_pool, author_revprop->data);
-  else
-    (*details)->rev_author = _("unknown author");
-
-  /* Check for replacement. */
-  (*details)->replacing_node_kind = svn_node_none;
-  if ((*details)->added_rev > 0)
-    {
-      svn_node_kind_t replaced_node_kind;
-
-      SVN_ERR(svn_ra_check_path(ra_session, "",
-                                rev_below((*details)->added_rev),
-                                &replaced_node_kind, scratch_pool));
-      if (replaced_node_kind != svn_node_none)
-        SVN_ERR(svn_ra_check_path(ra_session, "", (*details)->added_rev,
-                                  &(*details)->replacing_node_kind,
-                                  scratch_pool));
-    }
-
-  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_PROP_REVISION_AUTHOR,
+                          &author_revprop, scratch_pool));
+  (*details)->deleted_rev = SVN_INVALID_REVNUM;
+  (*details)->added_rev = b.added_rev;
+  (*details)->repos_relpath = apr_pstrdup(result_pool, b.repos_relpath);
+  if (author_revprop)
+    (*details)->rev_author = apr_pstrdup(result_pool, author_revprop->data);
+  else
+    (*details)->rev_author = _("unknown author");
 
-          svn_pool_clear(iterpool);
+  /* Check for replacement. */
+  (*details)->replacing_node_kind = svn_node_none;
+  if ((*details)->added_rev > 0)
+    {
+      svn_node_kind_t replaced_node_kind;
 
-          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);
+      SVN_ERR(svn_ra_check_path(ra_session, "",
+                                rev_below((*details)->added_rev),
+                                &replaced_node_kind, scratch_pool));
+      if (replaced_node_kind != svn_node_none)
+        SVN_ERR(svn_ra_check_path(ra_session, "", (*details)->added_rev,
+                                  &(*details)->replacing_node_kind,
+                                  scratch_pool));
     }
 
   return SVN_NO_ERROR;
@@ -9004,6 +9072,11 @@ resolve_local_move_file_merge(svn_client
   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;
 
@@ -9019,12 +9092,28 @@ resolve_local_move_file_merge(svn_client
             NULL, conflict, scratch_pool,
             scratch_pool));
 
-  if (details->wc_siblings) /* local missing */
-    merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings,
-                                         details->preferred_sibling_idx,
-                                         const char *);
-  else /* local move */
-    merge_target_abspath = details->moved_to_abspath;
+  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
+    return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                             _("Corresponding working copy node not found "
+                               "for '%s'"),
+                             svn_dirent_local_style(
+                               svn_dirent_skip_ancestor(
+                                 wcroot_abspath, conflict->local_abspath),
+                             scratch_pool));
 
   SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx,
                              merge_target_abspath,
@@ -10291,64 +10380,30 @@ configure_option_local_move_file_merge(s
                                  scratch_pool, scratch_pool));
 
       details = conflict->tree_conflict_local_details;
-      if (details != NULL && details->moves != NULL)
+      if (details != NULL && details->moves != NULL &&
+          details->move_target_repos_relpath != NULL)
         {
-          apr_hash_t *wc_move_targets = apr_hash_make(scratch_pool);
-          apr_pool_t *iterpool;
-          int i;
-
-          iterpool = svn_pool_create(scratch_pool);
-          for (i = 0; i < details->moves->nelts; i++)
-            {
-              struct repos_move_info *move;
-
-              svn_pool_clear(iterpool);
-              move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *);
-              SVN_ERR(follow_move_chains(wc_move_targets, move, ctx,
-                                         conflict->local_abspath,
-                                         svn_node_file,
-                                         incoming_new_repos_relpath,
-                                         incoming_new_pegrev,
-                                         scratch_pool, iterpool));
-            }
-          svn_pool_destroy(iterpool);
-
-          if (apr_hash_count(wc_move_targets) > 0)
-            {
-              apr_array_header_t *move_target_repos_relpaths;
-              const svn_sort__item_t *item;
-              apr_array_header_t *moved_to_abspaths;
-              const char *description;
+          apr_array_header_t *moves;
+          const char *moved_to_abspath;
+          const char *description;
 
-              /* 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(
-                                             wc_move_targets,
-                                             svn_sort_compare_items_as_paths,
-                                             scratch_pool);
-              item = &APR_ARRAY_IDX(move_target_repos_relpaths,
-                                    0, svn_sort__item_t);
-              moved_to_abspaths = item->value;
-              details->moved_to_abspath =
-                apr_pstrdup(conflict->pool,
-                            APR_ARRAY_IDX(moved_to_abspaths, 0, const char *));
+          moves = svn_hash_gets(details->wc_move_targets,
+                                details->move_target_repos_relpath);
+          moved_to_abspath =
+            APR_ARRAY_IDX(moves, details->wc_move_target_idx, const char *);
 
-              description =
-                apr_psprintf(
-                  scratch_pool, _("apply changes to move destination '%s'"),
-                  svn_dirent_local_style(
-                    svn_dirent_skip_ancestor(wcroot_abspath,
-                                             details->moved_to_abspath),
-                    scratch_pool));
+          description =
+            apr_psprintf(
+              scratch_pool, _("apply changes to move destination '%s'"),
+              svn_dirent_local_style(
+                svn_dirent_skip_ancestor(wcroot_abspath, moved_to_abspath),
+                scratch_pool));
 
-              add_resolution_option(
-                options, conflict,
-                svn_client_conflict_option_local_move_file_text_merge,
-                _("Apply to move destination"),
-                description, resolve_local_move_file_merge);
-            }
-          else
-            details->moved_to_abspath = NULL;
+          add_resolution_option(
+            options, conflict,
+            svn_client_conflict_option_local_move_file_text_merge,
+            _("Apply to move destination"),
+            description, resolve_local_move_file_merge);
         }
     }
 
@@ -10439,43 +10494,24 @@ configure_option_sibling_move_merge(svn_
   return SVN_NO_ERROR;
 }
 
-svn_error_t *
-svn_client_conflict_option_get_moved_to_repos_relpath_candidates(
+/* Return a copy of the repos replath candidate list. */
+static svn_error_t *
+get_repos_relpath_candidates(
   apr_array_header_t **possible_moved_to_repos_relpaths,
-  svn_client_conflict_option_t *option,
+  apr_hash_t *wc_move_targets,
   apr_pool_t *result_pool,
   apr_pool_t *scratch_pool)
 {
-  svn_client_conflict_t *conflict = option->conflict;
-  struct conflict_tree_incoming_delete_details *details;
-  const char *victim_abspath;
   apr_array_header_t *sorted_repos_relpaths;
   int i;
 
-  SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) ==
-                 svn_client_conflict_option_incoming_move_file_text_merge ||
-                 svn_client_conflict_option_get_id(option) ==
-                 svn_client_conflict_option_incoming_move_dir_merge);
-
-  victim_abspath = svn_client_conflict_get_local_abspath(conflict);
-  details = conflict->tree_conflict_incoming_details;
-  if (details == NULL || details->wc_move_targets == NULL)
-    return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                             _("Getting a list of possible move targets "
-                               "requires details for tree conflict at '%s' "
-                               "to be fetched from the repository first"),
-                            svn_dirent_local_style(victim_abspath,
-                                                   scratch_pool));
-
-  /* Return a copy of the repos replath candidate list. */
-  sorted_repos_relpaths = svn_sort__hash(details->wc_move_targets,
+  sorted_repos_relpaths = svn_sort__hash(wc_move_targets,
                                          svn_sort_compare_items_as_paths,
                                          scratch_pool);
 
-  *possible_moved_to_repos_relpaths = apr_array_make(
-                                        result_pool,
-                                        sorted_repos_relpaths->nelts,
-                                        sizeof (const char *));
+  *possible_moved_to_repos_relpaths =
+    apr_array_make(result_pool, sorted_repos_relpaths->nelts,
+                   sizeof (const char *));
   for (i = 0; i < sorted_repos_relpaths->nelts; i++)
     {
       svn_sort__item_t item;
@@ -10491,37 +10527,88 @@ svn_client_conflict_option_get_moved_to_
 }
 
 svn_error_t *
-svn_client_conflict_option_set_moved_to_repos_relpath(
+svn_client_conflict_option_get_moved_to_repos_relpath_candidates(
+  apr_array_header_t **possible_moved_to_repos_relpaths,
   svn_client_conflict_option_t *option,
-  int preferred_move_target_idx,
-  svn_client_ctx_t *ctx,
+  apr_pool_t *result_pool,
   apr_pool_t *scratch_pool)
 {
   svn_client_conflict_t *conflict = option->conflict;
-  struct conflict_tree_incoming_delete_details *details;
   const char *victim_abspath;
-  apr_array_header_t *move_target_repos_relpaths;
-  svn_sort__item_t item;
-  const char *move_target_repos_relpath;
-  apr_hash_index_t *hi;
+  svn_wc_operation_t operation;
+  svn_wc_conflict_action_t incoming_change;
+  svn_wc_conflict_reason_t local_change;
 
   SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) ==
                  svn_client_conflict_option_incoming_move_file_text_merge ||
                  svn_client_conflict_option_get_id(option) ==
-                 svn_client_conflict_option_incoming_move_dir_merge);
+                 svn_client_conflict_option_local_move_file_text_merge ||
+                 svn_client_conflict_option_get_id(option) ==
+                 svn_client_conflict_option_incoming_move_dir_merge ||
+                 svn_client_conflict_option_get_id(option) ==
+                 svn_client_conflict_option_sibling_move_file_text_merge ||
+                 svn_client_conflict_option_get_id(option) ==
+                 svn_client_conflict_option_sibling_move_dir_merge);
 
   victim_abspath = svn_client_conflict_get_local_abspath(conflict);
-  details = conflict->tree_conflict_incoming_details;
-  if (details == NULL || details->wc_move_targets == NULL)
-    return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                             _("Setting a move target requires details "
-                               "for tree conflict at '%s' to be fetched "
-                               "from the repository first"),
-                            svn_dirent_local_style(victim_abspath,
-                                                   scratch_pool));
+  operation = svn_client_conflict_get_operation(conflict);
+  incoming_change = svn_client_conflict_get_incoming_change(conflict);
+  local_change = svn_client_conflict_get_local_change(conflict);
+
+  if (operation == svn_wc_operation_merge &&
+      incoming_change == svn_wc_conflict_action_edit &&
+      local_change == svn_wc_conflict_reason_missing)
+    {
+      struct conflict_tree_local_missing_details *details;
+
+      details = conflict->tree_conflict_local_details;
+      if (details == NULL || details->wc_move_targets == NULL)
+        return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                                 _("Getting a list of possible move targets "
+                                   "requires details for tree conflict at '%s' "
+                                   "to be fetched from the repository first"),
+                                svn_dirent_local_style(victim_abspath,
+                                                       scratch_pool));
+
+      SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths,
+                                           details->wc_move_targets,
+                                           result_pool, scratch_pool));
+    }
+  else
+    {
+      struct conflict_tree_incoming_delete_details *details;
+
+      details = conflict->tree_conflict_incoming_details;
+      if (details == NULL || details->wc_move_targets == NULL)
+        return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                                 _("Getting a list of possible move targets "
+                                   "requires details for tree conflict at '%s' "
+                                   "to be fetched from the repository first"),
+                                svn_dirent_local_style(victim_abspath,
+                                                       scratch_pool));
+
+      SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths,
+                                           details->wc_move_targets,
+                                           result_pool, scratch_pool));
+    }
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+set_wc_move_target(const char **new_hash_key,
+                   apr_hash_t *wc_move_targets,
+                   int preferred_move_target_idx,
+                   const char *victim_abspath,
+                   apr_pool_t *scratch_pool)
+{
+  apr_array_header_t *move_target_repos_relpaths;
+  svn_sort__item_t item;
+  const char *move_target_repos_relpath;
+  apr_hash_index_t *hi;
 
   if (preferred_move_target_idx < 0 ||
-      preferred_move_target_idx >= apr_hash_count(details->wc_move_targets))
+      preferred_move_target_idx >= apr_hash_count(wc_move_targets))
     return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
                              _("Index '%d' is out of bounds of the possible "
                                "move target list for '%s'"),
@@ -10530,15 +10617,14 @@ svn_client_conflict_option_set_moved_to_
                                                    scratch_pool));
 
   /* Translate the index back into a hash table key. */
-  move_target_repos_relpaths =
-    svn_sort__hash(details->wc_move_targets,
-                   svn_sort_compare_items_as_paths,
-                   scratch_pool);
+  move_target_repos_relpaths = svn_sort__hash(wc_move_targets,
+                                              svn_sort_compare_items_as_paths,
+                                              scratch_pool);
   item = APR_ARRAY_IDX(move_target_repos_relpaths, preferred_move_target_idx,
                        svn_sort__item_t);
   move_target_repos_relpath = item.key;
   /* Find our copy of the hash key and remember the user's preference. */
-  for (hi = apr_hash_first(scratch_pool, details->wc_move_targets);
+  for (hi = apr_hash_first(scratch_pool, wc_move_targets);
        hi != NULL;
        hi = apr_hash_next(hi))
     {
@@ -10546,16 +10632,7 @@ svn_client_conflict_option_set_moved_to_
 
       if (strcmp(move_target_repos_relpath, repos_relpath) == 0)
         {
-          details->move_target_repos_relpath = repos_relpath;
-          details->wc_move_target_idx = 0;
-          /* Update option description. */
-          SVN_ERR(describe_incoming_move_merge_conflict_option(
-                    &option->description,
-                    conflict, ctx,
-                    details,
-                    conflict->pool,
-                    scratch_pool));
-
+          *new_hash_key = repos_relpath;
           return SVN_NO_ERROR;
         }
     }
@@ -10569,6 +10646,92 @@ svn_client_conflict_option_set_moved_to_
 }
 
 svn_error_t *
+svn_client_conflict_option_set_moved_to_repos_relpath(
+  svn_client_conflict_option_t *option,
+  int preferred_move_target_idx,
+  svn_client_ctx_t *ctx,
+  apr_pool_t *scratch_pool)
+{
+  svn_client_conflict_t *conflict = option->conflict;
+  const char *victim_abspath;
+  svn_wc_operation_t operation;
+  svn_wc_conflict_action_t incoming_change;
+  svn_wc_conflict_reason_t local_change;
+
+  SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) ==
+                 svn_client_conflict_option_incoming_move_file_text_merge ||
+                 svn_client_conflict_option_get_id(option) ==
+                 svn_client_conflict_option_local_move_file_text_merge ||
+                 svn_client_conflict_option_get_id(option) ==
+                 svn_client_conflict_option_incoming_move_dir_merge ||
+                 svn_client_conflict_option_get_id(option) ==
+                 svn_client_conflict_option_sibling_move_file_text_merge ||
+                 svn_client_conflict_option_get_id(option) ==
+                 svn_client_conflict_option_sibling_move_dir_merge);
+
+  victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+  operation = svn_client_conflict_get_operation(conflict);
+  incoming_change = svn_client_conflict_get_incoming_change(conflict);
+  local_change = svn_client_conflict_get_local_change(conflict);
+  
+  if (operation == svn_wc_operation_merge &&
+      incoming_change == svn_wc_conflict_action_edit &&
+      local_change == svn_wc_conflict_reason_missing)
+    {
+      struct conflict_tree_local_missing_details *details;
+
+      details = conflict->tree_conflict_local_details;
+      if (details == NULL || details->wc_move_targets == NULL)
+        return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                                 _("Setting a move target requires details "
+                                   "for tree conflict at '%s' to be fetched "
+                                   "from the repository first"),
+                                svn_dirent_local_style(victim_abspath,
+                                                       scratch_pool));
+
+      SVN_ERR(set_wc_move_target(&details->move_target_repos_relpath,
+                                 details->wc_move_targets,
+                                 preferred_move_target_idx,
+                                 victim_abspath, scratch_pool));
+      details->wc_move_target_idx = 0;
+
+      /* Update option description. */
+      SVN_ERR(conflict_tree_get_description_local_missing(
+                &option->description, conflict, ctx,
+                conflict->pool, scratch_pool));
+    }
+  else
+    {
+      struct conflict_tree_incoming_delete_details *details;
+
+      details = conflict->tree_conflict_incoming_details;
+      if (details == NULL || details->wc_move_targets == NULL)
+        return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                                 _("Setting a move target requires details "
+                                   "for tree conflict at '%s' to be fetched "
+                                   "from the repository first"),
+                                svn_dirent_local_style(victim_abspath,
+                                                       scratch_pool));
+
+      SVN_ERR(set_wc_move_target(&details->move_target_repos_relpath,
+                                 details->wc_move_targets,
+                                 preferred_move_target_idx,
+                                 victim_abspath, scratch_pool));
+      details->wc_move_target_idx = 0;
+
+      /* Update option description. */
+      SVN_ERR(describe_incoming_move_merge_conflict_option(
+                &option->description,
+                conflict, ctx,
+                details,
+                conflict->pool,
+                scratch_pool));
+    }
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
 svn_client_conflict_option_get_moved_to_abspath_candidates(
   apr_array_header_t **possible_moved_to_abspaths,
   svn_client_conflict_option_t *option,
@@ -10606,7 +10769,7 @@ svn_client_conflict_option_get_moved_to_
 
       details = conflict->tree_conflict_local_details;
       if (details == NULL ||
-          (details->moved_to_abspath == NULL && details->wc_siblings == NULL))
+          (details->wc_move_targets == NULL && details->wc_siblings == NULL))
        return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
                                 _("Getting a list of possible move siblings "
                                   "requires details for tree conflict at '%s' "
@@ -10616,9 +10779,22 @@ svn_client_conflict_option_get_moved_to_
 
        *possible_moved_to_abspaths = apr_array_make(result_pool, 1,
                                                     sizeof (const char *));
-      if (details->moved_to_abspath)
-         APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
-           apr_pstrdup(result_pool, details->moved_to_abspath);
+      if (details->wc_move_targets)
+        {
+          apr_array_header_t *move_target_wc_abspaths;
+          move_target_wc_abspaths =
+            svn_hash_gets(details->wc_move_targets,
+                          details->move_target_repos_relpath);
+          for (i = 0; i < move_target_wc_abspaths->nelts; i++)
+            {
+              const char *moved_to_abspath;
+
+              moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i,
+                                               const char *);
+              APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
+                apr_pstrdup(result_pool, moved_to_abspath);
+            }
+        }
 
       /* ### Siblings are actually 'corresponding nodes', not 'move targets'.
          ### But we provide them here to avoid another API function. */
@@ -10715,35 +10891,63 @@ svn_client_conflict_option_set_moved_to_
                                  scratch_pool));
 
       details = conflict->tree_conflict_local_details;
-      if (details == NULL || details->wc_siblings == NULL)
+      if (details == NULL || (details->wc_siblings == NULL &&
+          details->wc_move_targets == NULL))
        return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                                _("Setting a move sibling requires details "
+                                _("Setting a move target requires details "
                                   "for tree conflict at '%s' to be fetched "
                                   "from the repository first"),
                                 svn_dirent_local_style(victim_abspath,
                                                        scratch_pool));
 
-      if (preferred_move_target_idx < 0 ||
-          preferred_move_target_idx > details->wc_siblings->nelts)
-        return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
-                                 _("Index '%d' is out of bounds of the possible "
-                                   "move sibling list for '%s'"),
-                                preferred_move_target_idx,
-                                svn_dirent_local_style(victim_abspath,
-                                                       scratch_pool));
-      /* Record the user's preference. */
-      details->preferred_sibling_idx = preferred_move_target_idx;
+      if (details->wc_siblings)
+        {
+          if (preferred_move_target_idx < 0 ||
+              preferred_move_target_idx > details->wc_siblings->nelts)
+            return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+                                     _("Index '%d' is out of bounds of the "
+                                       "possible move sibling list for '%s'"),
+                                    preferred_move_target_idx,
+                                    svn_dirent_local_style(victim_abspath,
+                                                           scratch_pool));
+          /* Record the user's preference. */
+          details->preferred_sibling_idx = preferred_move_target_idx;
 
-      /* Update option description. */
-      preferred_sibling = APR_ARRAY_IDX(details->wc_siblings,
-                                        details->preferred_sibling_idx,
-                                        const char *);
-      option->description =
-        apr_psprintf(
-          conflict->pool, _("apply changes to '%s'"),
-          svn_dirent_local_style(
-            svn_dirent_skip_ancestor(wcroot_abspath, preferred_sibling),
-            scratch_pool));
+          /* Update option description. */
+          preferred_sibling = APR_ARRAY_IDX(details->wc_siblings,
+                                            details->preferred_sibling_idx,
+                                            const char *);
+          option->description =
+            apr_psprintf(
+              conflict->pool, _("apply changes to '%s'"),
+              svn_dirent_local_style(
+                svn_dirent_skip_ancestor(wcroot_abspath, preferred_sibling),
+                scratch_pool));
+        }
+      else if (details->wc_move_targets)
+       {
+          apr_array_header_t *move_target_wc_abspaths;
+          move_target_wc_abspaths =
+            svn_hash_gets(details->wc_move_targets,
+                          details->move_target_repos_relpath);
+
+          if (preferred_move_target_idx < 0 ||
+              preferred_move_target_idx > move_target_wc_abspaths->nelts)
+            return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+                                     _("Index '%d' is out of bounds of the possible "
+                                       "move target list for '%s'"),
+                                    preferred_move_target_idx,
+                                    svn_dirent_local_style(victim_abspath,
+                                                           scratch_pool));
+
+          /* Record the user's preference. */
+          details->wc_move_target_idx = preferred_move_target_idx;
+
+          /* Update option description. */
+          SVN_ERR(conflict_tree_get_description_local_missing(
+                    &option->description, conflict, ctx,
+                    conflict->pool, scratch_pool));
+       }
     }
   else
     {

Modified: subversion/trunk/subversion/svn/conflict-callbacks.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/svn/conflict-callbacks.c?rev=1840995&r1=1840994&r2=1840995&view=diff
==============================================================================
--- subversion/trunk/subversion/svn/conflict-callbacks.c (original)
+++ subversion/trunk/subversion/svn/conflict-callbacks.c Sun Sep 16 10:43:17 2018
@@ -1535,7 +1535,11 @@ build_tree_conflict_options(
         *all_options_are_dumb = FALSE;
 
       if (id == svn_client_conflict_option_incoming_move_file_text_merge ||
-          id == svn_client_conflict_option_incoming_move_dir_merge)
+          id == svn_client_conflict_option_incoming_move_dir_merge ||
+          id == svn_client_conflict_option_local_move_file_text_merge ||
+          id == svn_client_conflict_option_local_move_dir_merge ||
+          id == svn_client_conflict_option_sibling_move_file_text_merge ||
+          id == svn_client_conflict_option_sibling_move_dir_merge)
         {
           SVN_ERR(
             svn_client_conflict_option_get_moved_to_repos_relpath_candidates(
@@ -1545,13 +1549,6 @@ build_tree_conflict_options(
                     possible_moved_to_abspaths, builtin_option,
                     result_pool, iterpool));
         }
-      else if (id == svn_client_conflict_option_local_move_file_text_merge ||
-          id == svn_client_conflict_option_local_move_dir_merge ||
-          id == svn_client_conflict_option_sibling_move_file_text_merge ||
-          id == svn_client_conflict_option_sibling_move_dir_merge)
-          SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates(
-                    possible_moved_to_abspaths, builtin_option,
-                    result_pool, iterpool));
     }
 
   svn_pool_destroy(iterpool);
@@ -1832,7 +1829,30 @@ handle_tree_conflict(svn_boolean_t *reso
                 svn_client_conflict_option_find_by_id( 
                   options, svn_client_conflict_option_incoming_move_dir_merge);
             }
-
+          if (conflict_option == NULL)
+            {
+              conflict_option =
+                svn_client_conflict_option_find_by_id( 
+                  options, svn_client_conflict_option_local_move_file_text_merge);
+            }
+          if (conflict_option == NULL)
+            {
+              conflict_option =
+                svn_client_conflict_option_find_by_id( 
+                  options, svn_client_conflict_option_local_move_dir_merge);
+            }
+          if (conflict_option == NULL)
+            {
+              conflict_option =
+                svn_client_conflict_option_find_by_id( 
+                  options, svn_client_conflict_option_sibling_move_file_text_merge);
+            }
+          if (conflict_option == NULL)
+            {
+              conflict_option =
+                svn_client_conflict_option_find_by_id( 
+                  options, svn_client_conflict_option_sibling_move_dir_merge);
+            }
           if (conflict_option)
             {
               SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath(

Modified: subversion/trunk/subversion/tests/libsvn_client/conflicts-test.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/libsvn_client/conflicts-test.c?rev=1840995&r1=1840994&r2=1840995&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/libsvn_client/conflicts-test.c (original)
+++ subversion/trunk/subversion/tests/libsvn_client/conflicts-test.c Sun Sep 16 10:43:17 2018
@@ -5700,6 +5700,185 @@ test_cherry_pick_post_move_edit_dir(cons
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+test_local_missing_abiguous_moves(const svn_test_opts_t *opts,
+                                  apr_pool_t *pool)
+{
+  svn_test__sandbox_t *b = apr_palloc(pool, sizeof(*b));
+  svn_opt_revision_t opt_rev;
+  svn_client_ctx_t *ctx;
+  svn_client_conflict_t *conflict;
+  apr_array_header_t *options;
+  svn_client_conflict_option_t *option;
+  apr_array_header_t *possible_moved_to_repos_relpaths;
+  apr_array_header_t *possible_moved_to_abspaths;
+  struct status_baton sb;
+  struct svn_client_status_t *status;
+  svn_stringbuf_t *buf;
+
+  SVN_ERR(svn_test__sandbox_create(b, "local_missing_ambiguous_moves", opts,
+                                   pool));
+
+  SVN_ERR(sbox_add_and_commit_greek_tree(b)); /* r1 */
+
+  /* Create a copy of node "A" (the "trunk") to "A1" (the "branch"). */
+  SVN_ERR(sbox_wc_copy(b, "A", "A1"));
+  SVN_ERR(sbox_wc_commit(b, "")); /* r2 */
+
+  SVN_ERR(sbox_wc_update(b, "", SVN_INVALID_REVNUM));
+  /* Copy a file across branch boundaries (gives ambiguous WC targets later). */
+  SVN_ERR(sbox_wc_copy(b, "A/mu", "A1/mu-copied-from-A"));
+  /* Create an ambiguous move with the "trunk". */
+  SVN_ERR(sbox_wc_copy(b, "A/mu", "A/mu-copied"));
+  SVN_ERR(sbox_wc_move(b, "A/mu", "A/mu-moved"));
+  SVN_ERR(sbox_wc_commit(b, "")); /* r3 */
+
+  /* Modify the moved file on the "branch". */
+  SVN_ERR(sbox_file_write(b, "A1/mu", "Modified content." APR_EOL_STR));
+  SVN_ERR(sbox_wc_commit(b, "")); /* r4 */
+  SVN_ERR(sbox_wc_update(b, "", SVN_INVALID_REVNUM));
+
+  /* Merge "A1" ("branch") into "A" ("trunk"). */
+  opt_rev.kind = svn_opt_revision_head;
+  opt_rev.value.number = SVN_INVALID_REVNUM;
+  SVN_ERR(svn_test__create_client_ctx(&ctx, b, pool));
+  SVN_ERR(svn_client_merge_peg5(svn_path_url_add_component2(b->repos_url, "A1",
+                                                            pool),
+                                NULL, &opt_rev, sbox_wc_path(b, "A"),
+                                svn_depth_infinity,
+                                FALSE, FALSE, FALSE, FALSE, FALSE, FALSE,
+                                NULL, ctx, pool));
+
+  SVN_ERR(svn_client_conflict_get(&conflict, sbox_wc_path(b, "A/mu"),
+                                  ctx, b->pool, b->pool));
+  {
+    svn_client_conflict_option_id_t expected_opts[] = {
+      svn_client_conflict_option_postpone,
+      svn_client_conflict_option_accept_current_wc_state,
+      -1 /* end of list */
+    };
+    SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts,
+                                         b->pool));
+  }
+  SVN_ERR(svn_client_conflict_tree_get_details(conflict, ctx, b->pool));
+  {
+    svn_client_conflict_option_id_t expected_opts[] = {
+      svn_client_conflict_option_postpone,
+      svn_client_conflict_option_accept_current_wc_state,
+      svn_client_conflict_option_local_move_file_text_merge,
+      -1 /* end of list */
+    };
+    SVN_ERR(assert_tree_conflict_options(conflict, ctx, expected_opts,
+                                         b->pool));
+  }
+
+  SVN_ERR(svn_client_conflict_tree_get_resolution_options(&options, conflict,
+                                                          ctx, b->pool,
+                                                          b->pool));
+  option = svn_client_conflict_option_find_by_id(
+             options, svn_client_conflict_option_local_move_file_text_merge);
+  SVN_TEST_ASSERT(option != NULL);
+
+	/*
+	 * Possible repository destinations for moved-away 'A/mu' are:
+	 *  (1): '^/A/mu-copied'
+	 *  (2): '^/A/mu-moved'
+	 *  (3): '^/A1/mu-copied-from-A'
+	 */
+  SVN_ERR(svn_client_conflict_option_get_moved_to_repos_relpath_candidates(
+            &possible_moved_to_repos_relpaths, option, b->pool, b->pool));
+  SVN_TEST_INT_ASSERT(possible_moved_to_repos_relpaths->nelts, 3);
+  SVN_TEST_STRING_ASSERT(
+    APR_ARRAY_IDX(possible_moved_to_repos_relpaths, 0, const char *),
+    "A/mu-copied");
+  SVN_TEST_STRING_ASSERT(
+    APR_ARRAY_IDX(possible_moved_to_repos_relpaths, 1, const char *),
+    "A/mu-moved");
+  SVN_TEST_STRING_ASSERT(
+    APR_ARRAY_IDX(possible_moved_to_repos_relpaths, 2, const char *),
+    "A1/mu-copied-from-A");
+
+  /* Move target for "A/mu-copied" (selected by default) is not ambiguous. */
+  SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates(
+            &possible_moved_to_abspaths, option, b->pool, b->pool));
+  SVN_TEST_INT_ASSERT(possible_moved_to_abspaths->nelts, 1);
+  SVN_TEST_STRING_ASSERT(
+    APR_ARRAY_IDX(possible_moved_to_abspaths, 0, const char *),
+    sbox_wc_path(b, "A/mu-copied"));
+
+  /* Move target for "A/mu-moved" is not ambiguous. */
+  SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath(option, 1,
+                                                                ctx, b->pool));
+  SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates(
+            &possible_moved_to_abspaths, option, b->pool, b->pool));
+  SVN_TEST_INT_ASSERT(possible_moved_to_abspaths->nelts, 1);
+  SVN_TEST_STRING_ASSERT(
+    APR_ARRAY_IDX(possible_moved_to_abspaths, 0, const char *),
+    sbox_wc_path(b, "A/mu-moved"));
+
+  /* Select move target "A1/mu-copied-from-A". */
+  SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath(option, 2,
+                                                                ctx, b->pool));
+
+  /*
+	 * Possible working copy destinations for moved-away 'A/mu' are:
+	 *  (1): 'A/mu-copied-from-A'
+	 *  (2): 'A1/mu-copied-from-A'
+   */
+  SVN_ERR(svn_client_conflict_option_get_moved_to_abspath_candidates(
+            &possible_moved_to_abspaths, option, b->pool, b->pool));
+  SVN_TEST_INT_ASSERT(possible_moved_to_abspaths->nelts, 2);
+  SVN_TEST_STRING_ASSERT(
+    APR_ARRAY_IDX(possible_moved_to_abspaths, 0, const char *),
+    sbox_wc_path(b, "A/mu-copied-from-A"));
+  SVN_TEST_STRING_ASSERT(
+    APR_ARRAY_IDX(possible_moved_to_abspaths, 1, const char *),
+    sbox_wc_path(b, "A1/mu-copied-from-A"));
+
+  /* Select move target "A/mu-moved". */
+  SVN_ERR(svn_client_conflict_option_set_moved_to_repos_relpath(option, 1,
+                                                                ctx, b->pool));
+
+  /* Resolve the tree conflict. */
+  SVN_ERR(svn_client_conflict_tree_resolve_by_id(
+            conflict,
+            svn_client_conflict_option_local_move_file_text_merge, ctx,
+            b->pool));
+
+  /* The node "A/mu" should no longer exist. */
+  SVN_TEST_ASSERT_ERROR(svn_client_conflict_get(&conflict,
+                                                sbox_wc_path(b, "A/mu"),
+                                                ctx, pool, pool),
+                        SVN_ERR_WC_PATH_NOT_FOUND);
+
+  /* Ensure that the merged file has the expected status. */
+  opt_rev.kind = svn_opt_revision_working;
+  sb.result_pool = b->pool;
+  SVN_ERR(svn_client_status6(NULL, ctx, sbox_wc_path(b, "A/mu-moved"),
+                             &opt_rev, svn_depth_unknown, TRUE, TRUE,
+                             TRUE, TRUE, FALSE, TRUE, NULL,
+                             status_func, &sb, b->pool));
+  status = sb.status;
+  SVN_TEST_ASSERT(status->kind == svn_node_file);
+  SVN_TEST_ASSERT(status->versioned);
+  SVN_TEST_ASSERT(!status->conflicted);
+  SVN_TEST_ASSERT(status->node_status == svn_wc_status_modified);
+  SVN_TEST_ASSERT(status->text_status == svn_wc_status_modified);
+  SVN_TEST_ASSERT(status->prop_status == svn_wc_status_none);
+  SVN_TEST_ASSERT(!status->copied);
+  SVN_TEST_ASSERT(!status->switched);
+  SVN_TEST_ASSERT(!status->file_external);
+  SVN_TEST_ASSERT(status->moved_from_abspath == NULL);
+  SVN_TEST_ASSERT(status->moved_to_abspath == NULL);
+
+  /* And it should have expected contents. */
+  SVN_ERR(svn_stringbuf_from_file2(&buf, sbox_wc_path(b, "A/mu-moved"),
+                                   pool));
+  SVN_TEST_STRING_ASSERT(buf->data, "Modified content." APR_EOL_STR);
+
+  return SVN_NO_ERROR;
+}
+
 /* ========================================================================== */
 
 
@@ -5798,6 +5977,8 @@ static struct svn_test_descriptor_t test
                        "do not suggest unrelated move targets (#4766)"),
     SVN_TEST_OPTS_PASS(test_cherry_pick_post_move_edit_dir,
                        "cherry-pick edit from moved directory"),
+    SVN_TEST_OPTS_PASS(test_local_missing_abiguous_moves,
+                       "local missing conflict with ambiguous moves"),
     SVN_TEST_NULL
   };