Modified: subversion/branches/swig-py3/subversion/libsvn_client/conflicts.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/swig-py3/subversion/libsvn_client/conflicts.c?rev=1862754&r1=1862753&r2=1862754&view=diff
==============================================================================
--- subversion/branches/swig-py3/subversion/libsvn_client/conflicts.c (original)
+++ subversion/branches/swig-py3/subversion/libsvn_client/conflicts.c Mon Jul  
8 15:19:03 2019
@@ -8632,10 +8632,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,
@@ -8644,19 +8644,31 @@ resolve_incoming_move_file_text_merge(sv
       if (err)
         goto unlock_wc;
 
-      err = svn_wc__translated_stream(&working_stream, ctx->wc_ctx,
-                                      merge_source_abspath,
-                                      merge_source_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)
     {
@@ -8997,53 +9009,60 @@ unlock_wc:
   return SVN_NO_ERROR;
 }
 
-/* Implements conflict_option_resolve_func_t. */
+/* 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_incoming_move_dir_merge(svn_client_conflict_option_t *option,
-                                svn_client_conflict_t *conflict,
-                                svn_client_ctx_t *ctx,
-                                apr_pool_t *scratch_pool)
+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 *local_abspath;
+  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 *repos_uuid;
   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 *victim_repos_relpath;
-  svn_revnum_t victim_peg_rev;
-  const char *moved_to_repos_relpath;
-  svn_revnum_t moved_to_peg_rev;
-  struct conflict_tree_incoming_delete_details *details;
+  const char *incoming_moved_repos_relpath;
+  struct conflict_tree_incoming_delete_details *incoming_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_moved_to_abspath;
+  struct conflict_tree_local_missing_details *local_details;
+  apr_array_header_t *local_moves;
   svn_client__conflict_report_t *conflict_report;
-  svn_boolean_t is_copy;
-  svn_boolean_t is_modified;
+  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;
 
-  local_abspath = svn_client_conflict_get_local_abspath(conflict);
+  victim_abspath = svn_client_conflict_get_local_abspath(conflict);
   operation = svn_client_conflict_get_operation(conflict);
-  details = conflict->tree_conflict_incoming_details;
-  if (details == NULL || details->moves == NULL)
+  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(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(victim_abspath,
+                                                    scratch_pool));
 
   option_id = svn_client_conflict_option_get_id(option);
-  SVN_ERR_ASSERT(option_id ==
-                 svn_client_conflict_option_incoming_move_dir_merge);
+  SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_dir_merge);
                   
-  SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid,
+  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(
@@ -9055,186 +9074,78 @@ resolve_incoming_move_dir_merge(svn_clie
             NULL, conflict, scratch_pool,
             scratch_pool));
 
-  /* Get repository location of the moved-away node (the conflict victim). */
-  if (operation == svn_wc_operation_update ||
-      operation == svn_wc_operation_switch)
-    {
-      victim_repos_relpath = incoming_old_repos_relpath;
-      victim_peg_rev = incoming_old_pegrev;
-    }
-  else if (operation == svn_wc_operation_merge)
-    SVN_ERR(svn_wc__node_get_repos_info(&victim_peg_rev, &victim_repos_relpath,
-                                        NULL, NULL, ctx->wc_ctx, local_abspath,
-                                        scratch_pool, scratch_pool));
-
-  /* Get repository location of the moved-here node (incoming move). */
   possible_moved_to_abspaths =
-    svn_hash_gets(details->wc_move_targets,
-                  get_moved_to_repos_relpath(details, scratch_pool));
-  moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_abspaths,
-                                   details->wc_move_target_idx,
-                                   const char *);
+    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 *);
 
-  /* ### The following WC modifications should be atomic. */
+  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(local_abspath,
-                                            moved_to_abspath,
+            svn_dirent_get_longest_ancestor(victim_abspath,
+                                            local_moved_to_abspath,
                                             scratch_pool),
             scratch_pool, scratch_pool));
 
-  err = svn_wc__node_get_origin(&is_copy, &moved_to_peg_rev,
-                                &moved_to_repos_relpath,
-                                NULL, NULL, NULL, NULL,
-                                ctx->wc_ctx, moved_to_abspath, FALSE,
-                                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;
-  if (!is_copy && operation == svn_wc_operation_merge)
-    {
-      err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                              _("Cannot resolve tree conflict on '%s' "
-                                "(expected a copied item at '%s', but the "
-                                "item is not a copy)"),
-                              svn_dirent_local_style(local_abspath,
-                                                     scratch_pool),
-                              svn_dirent_local_style(moved_to_abspath,
-                                                     scratch_pool));
-      goto unlock_wc;
-    }
-
-  if (moved_to_repos_relpath == NULL || moved_to_peg_rev == SVN_INVALID_REVNUM)
-    {
-      err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                              _("Cannot resolve tree conflict on '%s' "
-                                "(could not determine origin of '%s')"),
-                              svn_dirent_local_style(local_abspath,
-                                                     scratch_pool),
-                              svn_dirent_local_style(moved_to_abspath,
-                                                     scratch_pool));
-      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);
+  /* 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;
 
-  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);
+  err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
   if (err)
     goto unlock_wc;
 
-  if (operation == svn_wc_operation_merge)
-    {
-      const char *move_target_url;
-      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));
-
-      /* 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_abspath, 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 YCA_URL@YCA_REV->MOVE_TARGET_URL@MERGE_RIGHT into move target. 
*/
-      move_target_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
-                                    get_moved_to_repos_relpath(details,
-                                                               scratch_pool),
-                                    SVN_VA_NULL);
-      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,
-                                     move_target_url, &incoming_new_opt_rev,
-                                     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;
-    }
-  else
-    {
-      SVN_ERR_ASSERT(operation == svn_wc_operation_update ||
-                     operation == svn_wc_operation_switch);
-
-      /* Merge local modifications into the incoming move target dir. */
-      err = svn_wc__has_local_mods(&is_modified, ctx->wc_ctx, local_abspath,
-                                   TRUE, ctx->cancel_func, ctx->cancel_baton,
-                                   scratch_pool);
-      if (err)
-        goto unlock_wc;
-
-      if (is_modified)
-        {
-          err = svn_wc__conflict_tree_update_incoming_move(ctx->wc_ctx,
-                                                           local_abspath,
-                                                           moved_to_abspath,
-                                                           ctx->cancel_func,
-                                                           ctx->cancel_baton,
-                                                           ctx->notify_func2,
-                                                           ctx->notify_baton2,
-                                                           scratch_pool);
-          if (err)
-            goto unlock_wc;
-        }
-
-      /* The move operation is part of our natural history.
-       * Delete the tree conflict victim (clears the tree conflict marker). */
-      err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
-                           NULL, NULL, /* don't allow user to cancel here */
-                           NULL, NULL, /* no extra notification */
-                           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);
     }
 
+  svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
+
   conflict->resolution_tree = option_id;
 
 unlock_wc:
@@ -9247,14 +9158,19 @@ unlock_wc:
 }
 
 /* Implements conflict_option_resolve_func_t.
- * Handles svn_client_conflict_option_local_move_file_text_merge
- * and svn_client_conflict_option_sibling_move_file_text_merge. */
+ * 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_local_move_file_merge(svn_client_conflict_option_t *option,
-                              svn_client_conflict_t *conflict,
-                              svn_client_ctx_t *ctx,
-                              apr_pool_t *scratch_pool)
+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;
@@ -9262,29 +9178,39 @@ resolve_local_move_file_merge(svn_client
   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_tmp_abspath;
-  const char *incoming_tmp_abspath;
-  apr_hash_t *ancestor_props;
-  apr_hash_t *incoming_props;
-  svn_stream_t *stream;
-  const char *url;
-  const char *corrected_url;
-  const char *old_session_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_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));
+  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;
 
-  details = conflict->tree_conflict_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_dir_move_merge);
+                  
   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
                                              conflict, scratch_pool,
                                              scratch_pool));
@@ -9297,162 +9223,149 @@ resolve_local_move_file_merge(svn_client
             NULL, conflict, scratch_pool,
             scratch_pool));
 
-  if (details->wc_siblings)
-    {
-      merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings,
-                                           details->preferred_sibling_idx,
-                                           const char *);
-    }
-  else if (details->wc_move_targets && details->move_target_repos_relpath)
-    {
-      apr_array_header_t *moves;
-      moves = svn_hash_gets(details->wc_move_targets,
-                            details->move_target_repos_relpath);
-      merge_target_abspath = APR_ARRAY_IDX(moves, details->wc_move_target_idx,
-                                           const char *);
-    }
-  else
-    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,
-                             scratch_pool, scratch_pool));
-
-  /* Fetch the common ancestor file's content. */
-  SVN_ERR(svn_stream_open_unique(&stream, &ancestor_tmp_abspath, wc_tmpdir,
-                                 svn_io_file_del_on_pool_cleanup,
-                                 scratch_pool, scratch_pool));
-  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,
-                                               url, NULL, NULL,
-                                               FALSE, FALSE, ctx,
-                                               scratch_pool, scratch_pool));
-  SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, stream, NULL,
-                          &ancestor_props, scratch_pool));
-  filter_props(ancestor_props, scratch_pool);
-
-  /* Close stream to flush the file to disk. */
-  SVN_ERR(svn_stream_close(stream));
-
-  /* Do the same for the incoming file's content. */
-  SVN_ERR(svn_stream_open_unique(&stream, &incoming_tmp_abspath, wc_tmpdir,
-                                 svn_io_file_del_on_pool_cleanup,
-                                 scratch_pool, scratch_pool));
-  url = svn_path_url_add_component2(repos_root_url,
-                                    incoming_new_repos_relpath,
-                                    scratch_pool);
-  SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
-                                            url, scratch_pool));
-  SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev, stream, NULL,
-                          &incoming_props, scratch_pool));
-  /* Close stream to flush the file to disk. */
-  SVN_ERR(svn_stream_close(stream));
-
-  filter_props(incoming_props, 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 *);
 
-  /* Create a property diff for the files. */
-  SVN_ERR(svn_prop_diffs(&propdiffs, incoming_props, ancestor_props,
-                         scratch_pool));
+  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(conflict->local_abspath,
-                                            merge_target_abspath,
+            svn_dirent_get_longest_ancestor(victim_abspath,
+                                            local_moved_to_abspath,
                                             scratch_pool),
             scratch_pool, scratch_pool));
 
-  /* Perform the file merge. */
-  err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
-                      ctx->wc_ctx,
-                      ancestor_tmp_abspath, incoming_tmp_abspath,
-                      merge_target_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 */
+  /* 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);
-  svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool);
   if (err)
-    return svn_error_compose_create(err,
-                                    svn_wc__release_write_lock(ctx->wc_ctx,
-                                                               lock_abspath,
-                                                               scratch_pool));
+    goto unlock_wc;
 
-  err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath,
-                                  scratch_pool);
-  err = svn_error_compose_create(err,
-                                 svn_wc__release_write_lock(ctx->wc_ctx,
-                                                            lock_abspath,
-                                                            scratch_pool));
+  /* 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)
-    return svn_error_trace(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;
 
-      /* Tell the world about the file merge that just happened. */
-      notify = svn_wc_create_notify(merge_target_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);
-
-      /* And also about the successfully resolved tree conflict. */
-      notify = svn_wc_create_notify(conflict->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);
     }
 
-  conflict->resolution_tree = svn_client_conflict_option_get_id(option);
+  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_local_move_dir_merge(svn_client_conflict_option_t *option,
-                             svn_client_conflict_t *conflict,
-                             svn_client_ctx_t *ctx,
-                             apr_pool_t *scratch_pool)
+resolve_incoming_move_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 *local_abspath;
+  svn_wc_operation_t operation;
   const char *lock_abspath;
   svn_error_t *err;
   const char *repos_root_url;
+  const char *repos_uuid;
   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_local_missing_details *details;
-  const char *merge_target_abspath;
+  const char *victim_repos_relpath;
+  svn_revnum_t victim_peg_rev;
+  const char *moved_to_repos_relpath;
+  svn_revnum_t moved_to_peg_rev;
+  struct conflict_tree_incoming_delete_details *details;
+  apr_array_header_t *possible_moved_to_abspaths;
+  const char *moved_to_abspath;
   const char *incoming_old_url;
-  const char *incoming_new_url;
   svn_opt_revision_t incoming_old_opt_rev;
-  svn_opt_revision_t incoming_new_opt_rev;
   svn_client__conflict_report_t *conflict_report;
+  svn_boolean_t is_copy;
+  svn_boolean_t is_modified;
 
-  details = conflict->tree_conflict_local_details;
+  local_abspath = svn_client_conflict_get_local_abspath(conflict);
+  operation = svn_client_conflict_get_operation(conflict);
+  details = conflict->tree_conflict_incoming_details;
+  if (details == NULL || 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(local_abspath,
+                                                   scratch_pool));
 
-  SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
+  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));
   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
@@ -9464,122 +9377,514 @@ resolve_local_move_dir_merge(svn_client_
             NULL, conflict, scratch_pool,
             scratch_pool));
 
-  if (details->wc_move_targets)
+  /* Get repository location of the moved-away node (the conflict victim). */
+  if (operation == svn_wc_operation_update ||
+      operation == svn_wc_operation_switch)
     {
-      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 *);
+      victim_repos_relpath = incoming_old_repos_relpath;
+      victim_peg_rev = incoming_old_pegrev;
     }
-  else
-    merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings,
-                                         details->preferred_sibling_idx,
-                                         const char *);
+  else if (operation == svn_wc_operation_merge)
+    SVN_ERR(svn_wc__node_get_repos_info(&victim_peg_rev, &victim_repos_relpath,
+                                        NULL, NULL, ctx->wc_ctx, local_abspath,
+                                        scratch_pool, scratch_pool));
+
+  /* Get repository location of the moved-here node (incoming move). */
+  possible_moved_to_abspaths =
+    svn_hash_gets(details->wc_move_targets,
+                  get_moved_to_repos_relpath(details, scratch_pool));
+  moved_to_abspath = APR_ARRAY_IDX(possible_moved_to_abspaths,
+                                   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(conflict->local_abspath,
-                                            merge_target_abspath,
+            svn_dirent_get_longest_ancestor(local_abspath,
+                                            moved_to_abspath,
                                             scratch_pool),
             scratch_pool, scratch_pool));
 
-  /* Resolve to current working copy state.
-   * svn_client__merge_locked() requires this. */
-  err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath,
-                                  scratch_pool);
+  err = svn_wc__node_get_origin(&is_copy, &moved_to_peg_rev,
+                                &moved_to_repos_relpath,
+                                NULL, NULL, NULL, NULL,
+                                ctx->wc_ctx, moved_to_abspath, FALSE,
+                                scratch_pool, scratch_pool);
   if (err)
     goto unlock_wc;
+  if (!is_copy && operation == svn_wc_operation_merge)
+    {
+      err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                              _("Cannot resolve tree conflict on '%s' "
+                                "(expected a copied item at '%s', but the "
+                                "item is not a copy)"),
+                              svn_dirent_local_style(local_abspath,
+                                                     scratch_pool),
+                              svn_dirent_local_style(moved_to_abspath,
+                                                     scratch_pool));
+      goto unlock_wc;
+    }
 
-  /* Merge outstanding changes to the 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_new_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
-                                 incoming_new_repos_relpath, SVN_VA_NULL);
-  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,
-                                 incoming_old_url, &incoming_old_opt_rev,
-                                 incoming_new_url, &incoming_new_opt_rev,
-                                 merge_target_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);
-unlock_wc:
-  svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool);
-  err = svn_error_compose_create(err,
-                                 svn_wc__release_write_lock(ctx->wc_ctx,
-                                                            lock_abspath,
-                                                            scratch_pool));
+  if (moved_to_repos_relpath == NULL || moved_to_peg_rev == SVN_INVALID_REVNUM)
+    {
+      err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                              _("Cannot resolve tree conflict on '%s' "
+                                "(could not determine origin of '%s')"),
+                              svn_dirent_local_style(local_abspath,
+                                                     scratch_pool),
+                              svn_dirent_local_style(moved_to_abspath,
+                                                     scratch_pool));
+      goto unlock_wc;
+    }
+
+  err = verify_local_state_for_incoming_delete(conflict, option, ctx,
+                                               scratch_pool);
   if (err)
-    return svn_error_trace(err);
+    goto unlock_wc;
+
+  if (operation == svn_wc_operation_merge)
+    {
+      const char *move_target_url;
+      svn_opt_revision_t incoming_new_opt_rev;
+
+      /* Revert the incoming move target directory. */
+      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. */
+      err = svn_wc__move2(ctx->wc_ctx, local_abspath, 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->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),
+                                    SVN_VA_NULL);
+      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,
+                                     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 */
+                                     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;
+    }
+  else
+    {
+      SVN_ERR_ASSERT(operation == svn_wc_operation_update ||
+                     operation == svn_wc_operation_switch);
+
+      /* Merge local modifications into the incoming move target dir. */
+      err = svn_wc__has_local_mods(&is_modified, ctx->wc_ctx, local_abspath,
+                                   TRUE, ctx->cancel_func, ctx->cancel_baton,
+                                   scratch_pool);
+      if (err)
+        goto unlock_wc;
+
+      if (is_modified)
+        {
+          err = svn_wc__conflict_tree_update_incoming_move(ctx->wc_ctx,
+                                                           local_abspath,
+                                                           moved_to_abspath,
+                                                           ctx->cancel_func,
+                                                           ctx->cancel_baton,
+                                                           ctx->notify_func2,
+                                                           ctx->notify_baton2,
+                                                           scratch_pool);
+          if (err)
+            goto unlock_wc;
+        }
+
+      /* The move operation is part of our natural history.
+       * Delete the tree conflict victim (clears the tree conflict marker). */
+      err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
+                           NULL, NULL, /* don't allow user to cancel here */
+                           NULL, NULL, /* no extra notification */
+                           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(merge_target_abspath,
-                                    svn_wc_notify_update_update,
-                                    scratch_pool);
-      if (conflict_report)
-        notify->content_state = svn_wc_notify_state_conflicted;
-      else
-        notify->content_state = svn_wc_notify_state_merged;
-      notify->kind = svn_node_dir;
-      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
-
-      /* And also about the successfully resolved tree conflict. */
-      notify = svn_wc_create_notify(conflict->local_abspath,
-                                    svn_wc_notify_resolved_tree,
+      notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
                                     scratch_pool);
       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
     }
 
-  conflict->resolution_tree = svn_client_conflict_option_get_id(option);
+  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.
+ * Handles svn_client_conflict_option_local_move_file_text_merge
+ * and svn_client_conflict_option_sibling_move_file_text_merge. */
 static svn_error_t *
-assert_text_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
+resolve_local_move_file_merge(svn_client_conflict_option_t *option,
+                              svn_client_conflict_t *conflict,
+                              svn_client_ctx_t *ctx,
+                              apr_pool_t *scratch_pool)
 {
-  svn_boolean_t text_conflicted;
-
-  SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, NULL, NULL,
-                                             conflict, scratch_pool,
-                                             scratch_pool));
-
-  SVN_ERR_ASSERT(text_conflicted); /* ### return proper error? */
+  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_tmp_abspath;
+  const char *incoming_tmp_abspath;
+  apr_hash_t *ancestor_props;
+  apr_hash_t *incoming_props;
+  svn_stream_t *stream;
+  const char *url;
+  const char *corrected_url;
+  const char *old_session_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_local_missing_details *details;
+  const char *merge_target_abspath;
+  const char *wcroot_abspath;
 
-  return SVN_NO_ERROR;
-}
+  SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+                             conflict->local_abspath, scratch_pool,
+                             scratch_pool));
 
-static svn_error_t *
-assert_prop_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
-{
-  apr_array_header_t *props_conflicted;
+  details = conflict->tree_conflict_local_details;
 
-  SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL,
+  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));
 
-  /* ### return proper error? */
-  SVN_ERR_ASSERT(props_conflicted && props_conflicted->nelts > 0);
+  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));
 
-  return SVN_NO_ERROR;
-}
+  SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx,
+                             merge_target_abspath,
+                             scratch_pool, scratch_pool));
 
-static svn_error_t *
-assert_tree_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
+  /* Fetch the common ancestor file's content. */
+  SVN_ERR(svn_stream_open_unique(&stream, &ancestor_tmp_abspath, wc_tmpdir,
+                                 svn_io_file_del_on_pool_cleanup,
+                                 scratch_pool, scratch_pool));
+  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,
+                                               url, NULL, NULL,
+                                               FALSE, FALSE, ctx,
+                                               scratch_pool, scratch_pool));
+  SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, stream, NULL,
+                          &ancestor_props, scratch_pool));
+  filter_props(ancestor_props, scratch_pool);
+
+  /* Close stream to flush the file to disk. */
+  SVN_ERR(svn_stream_close(stream));
+
+  /* Do the same for the incoming file's content. */
+  SVN_ERR(svn_stream_open_unique(&stream, &incoming_tmp_abspath, wc_tmpdir,
+                                 svn_io_file_del_on_pool_cleanup,
+                                 scratch_pool, scratch_pool));
+  url = svn_path_url_add_component2(repos_root_url,
+                                    incoming_new_repos_relpath,
+                                    scratch_pool);
+  SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
+                                            url, scratch_pool));
+  SVN_ERR(svn_ra_get_file(ra_session, "", incoming_new_pegrev, stream, NULL,
+                          &incoming_props, scratch_pool));
+  /* Close stream to flush the file to disk. */
+  SVN_ERR(svn_stream_close(stream));
+
+  filter_props(incoming_props, scratch_pool);
+
+  /* Create a property diff for the files. */
+  SVN_ERR(svn_prop_diffs(&propdiffs, incoming_props, ancestor_props,
+                         scratch_pool));
+
+  /* ### 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(conflict->local_abspath,
+                                            merge_target_abspath,
+                                            scratch_pool),
+            scratch_pool, scratch_pool));
+
+  /* Perform the file merge. */
+  err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
+                      ctx->wc_ctx,
+                      ancestor_tmp_abspath, incoming_tmp_abspath,
+                      merge_target_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);
+  svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool);
+  if (err)
+    return svn_error_compose_create(err,
+                                    svn_wc__release_write_lock(ctx->wc_ctx,
+                                                               lock_abspath,
+                                                               scratch_pool));
+
+  err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath,
+                                  scratch_pool);
+  err = svn_error_compose_create(err,
+                                 svn_wc__release_write_lock(ctx->wc_ctx,
+                                                            lock_abspath,
+                                                            scratch_pool));
+  if (err)
+    return svn_error_trace(err);
+
+  if (ctx->notify_func2)
+    {
+      svn_wc_notify_t *notify;
+
+      /* Tell the world about the file merge that just happened. */
+      notify = svn_wc_create_notify(merge_target_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);
+
+      /* And also about the successfully resolved tree conflict. */
+      notify = svn_wc_create_notify(conflict->local_abspath,
+                                    svn_wc_notify_resolved_tree,
+                                    scratch_pool);
+      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+    }
+
+  conflict->resolution_tree = svn_client_conflict_option_get_id(option);
+
+  return SVN_NO_ERROR;
+}
+
+/* Implements conflict_option_resolve_func_t. */
+static svn_error_t *
+resolve_local_move_dir_merge(svn_client_conflict_option_t *option,
+                             svn_client_conflict_t *conflict,
+                             svn_client_ctx_t *ctx,
+                             apr_pool_t *scratch_pool)
+{
+  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_local_missing_details *details;
+  const char *merge_target_abspath;
+  const char *incoming_old_url;
+  const char *incoming_new_url;
+  svn_opt_revision_t incoming_old_opt_rev;
+  svn_opt_revision_t incoming_new_opt_rev;
+  svn_client__conflict_report_t *conflict_report;
+
+  details = conflict->tree_conflict_local_details;
+
+  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));
+
+  if (details->wc_move_targets)
+    {
+      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
+    merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings,
+                                         details->preferred_sibling_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(conflict->local_abspath,
+                                            merge_target_abspath,
+                                            scratch_pool),
+            scratch_pool, scratch_pool));
+
+  /* Resolve to current working copy state.
+   * svn_client__merge_locked() requires this. */
+  err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath,
+                                  scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  /* Merge outstanding changes to the 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_new_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
+                                 incoming_new_repos_relpath, SVN_VA_NULL);
+  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,
+                                 incoming_old_url, &incoming_old_opt_rev,
+                                 incoming_new_url, &incoming_new_opt_rev,
+                                 merge_target_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);
+unlock_wc:
+  svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool);
+  err = svn_error_compose_create(err,
+                                 svn_wc__release_write_lock(ctx->wc_ctx,
+                                                            lock_abspath,
+                                                            scratch_pool));
+  if (err)
+    return svn_error_trace(err);
+
+  if (ctx->notify_func2)
+    {
+      svn_wc_notify_t *notify;
+
+      /* Tell the world about the file merge that just happened. */
+      notify = svn_wc_create_notify(merge_target_abspath,
+                                    svn_wc_notify_update_update,
+                                    scratch_pool);
+      if (conflict_report)
+        notify->content_state = svn_wc_notify_state_conflicted;
+      else
+        notify->content_state = svn_wc_notify_state_merged;
+      notify->kind = svn_node_dir;
+      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+
+      /* And also about the successfully resolved tree conflict. */
+      notify = svn_wc_create_notify(conflict->local_abspath,
+                                    svn_wc_notify_resolved_tree,
+                                    scratch_pool);
+      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+    }
+
+  conflict->resolution_tree = svn_client_conflict_option_get_id(option);
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+assert_text_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
+{
+  svn_boolean_t text_conflicted;
+
+  SVN_ERR(svn_client_conflict_get_conflicted(&text_conflicted, NULL, NULL,
+                                             conflict, scratch_pool,
+                                             scratch_pool));
+
+  SVN_ERR_ASSERT(text_conflicted); /* ### return proper error? */
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+assert_prop_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
+{
+  apr_array_header_t *props_conflicted;
+
+  SVN_ERR(svn_client_conflict_get_conflicted(NULL, &props_conflicted, NULL,
+                                             conflict, scratch_pool,
+                                             scratch_pool));
+
+  /* ### return proper error? */
+  SVN_ERR_ASSERT(props_conflicted && props_conflicted->nelts > 0);
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+assert_tree_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
 {
   svn_boolean_t tree_conflicted;
 
@@ -10383,323 +10688,1052 @@ configure_option_incoming_delete_accept(
 }
 
 static svn_error_t *
-describe_incoming_move_merge_conflict_option(
-  const char **description,
+describe_incoming_move_merge_conflict_option(
+  const char **description,
+  svn_client_conflict_t *conflict,
+  svn_client_ctx_t *ctx,
+  const char *moved_to_abspath,
+  apr_pool_t *result_pool,
+  apr_pool_t *scratch_pool)
+{
+  svn_wc_operation_t operation;
+  const char *victim_abspath;
+  svn_node_kind_t victim_node_kind;
+  const char *wcroot_abspath;
+
+  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)
+    {
+      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)
+            {
+              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(
+        result_pool, _("move and merge local changes from '%s' into '%s'"),
+        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));
+
+  return SVN_NO_ERROR;
+}
+
+/* Configure 'incoming move file merge' resolution option for
+ * a tree conflict. */
+static svn_error_t *
+configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict,
+                                          svn_client_ctx_t *ctx,
+                                          apr_array_header_t *options,
+                                          apr_pool_t *scratch_pool)
+{
+  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,
+            &incoming_old_kind, conflict, scratch_pool,
+            scratch_pool));
+  SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+            &incoming_new_repos_relpath, &incoming_new_pegrev,
+            &incoming_new_kind, conflict, scratch_pool,
+            scratch_pool));
+
+  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 &&
+      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)
+        return SVN_NO_ERROR;
+
+      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,
+                                                           moved_to_abspath,
+                                                           scratch_pool,
+                                                           scratch_pool));
+      add_resolution_option(
+        options, conflict,
+        svn_client_conflict_option_incoming_move_file_text_merge,
+        _("Move and merge"), description,
+        resolve_incoming_move_file_text_merge);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Configure 'incoming move dir merge' resolution option for
+ * a tree conflict. */
+static svn_error_t *
+configure_option_incoming_dir_merge(svn_client_conflict_t *conflict,
+                                    svn_client_ctx_t *ctx,
+                                    apr_array_header_t *options,
+                                    apr_pool_t *scratch_pool)
+{
+  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,
+            &incoming_old_kind, conflict, scratch_pool,
+            scratch_pool));
+  SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+            &incoming_new_repos_relpath, &incoming_new_pegrev,
+            &incoming_new_kind, conflict, scratch_pool,
+            scratch_pool));
+
+  if (victim_node_kind == svn_node_dir &&
+      incoming_old_kind == svn_node_dir &&
+      incoming_new_kind == svn_node_none &&
+      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)
+        return SVN_NO_ERROR;
+
+      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,
+                                                           moved_to_abspath,
+                                                           scratch_pool,
+                                                           scratch_pool));
+      add_resolution_option(options, conflict,
+                            svn_client_conflict_option_incoming_move_dir_merge,
+                            _("Move and merge"), description,
+                            resolve_incoming_move_dir_merge);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Configure 'local move file merge' resolution option for
+ * a tree conflict. */
+static svn_error_t *
+configure_option_local_move_file_or_dir_merge(
+  svn_client_conflict_t *conflict,
+  svn_client_ctx_t *ctx,
+  apr_array_header_t *options,
+  apr_pool_t *scratch_pool)
+{
+  svn_wc_operation_t operation;
+  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;
+
+  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);
+  SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+            &incoming_old_repos_relpath, &incoming_old_pegrev,
+            &incoming_old_kind, conflict, scratch_pool,
+            scratch_pool));
+  SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+            &incoming_new_repos_relpath, &incoming_new_pegrev,
+            &incoming_new_kind, conflict, scratch_pool,
+            scratch_pool));
+
+  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;
+      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;
+      if (details != NULL && details->moves != NULL &&
+          details->move_target_repos_relpath != NULL)
+        {
+          apr_array_header_t *moves;
+          const char *moved_to_abspath;
+          const char *description;
+
+          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, moved_to_abspath),
+                scratch_pool));
+
+          if ((incoming_old_kind == svn_node_file ||
+               incoming_old_kind == svn_node_none) &&
+              (incoming_new_kind == svn_node_file ||
+               incoming_new_kind == svn_node_none))
+            {
+              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
+            {
+              add_resolution_option(
+                options, conflict,
+                svn_client_conflict_option_local_move_dir_merge,
+                _("Apply to move destination"),
+                description, resolve_local_move_dir_merge);
+            }
+        }
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Configure 'sibling move file/dir merge' resolution option for
+ * a tree conflict. */
+static svn_error_t *
+configure_option_sibling_move_merge(svn_client_conflict_t *conflict,
+                                    svn_client_ctx_t *ctx,
+                                    apr_array_header_t *options,
+                                    apr_pool_t *scratch_pool)
+{
+  svn_wc_operation_t operation;
+  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;
+
+  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);
+  SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+            &incoming_old_repos_relpath, &incoming_old_pegrev,
+            &incoming_old_kind, conflict, scratch_pool,
+            scratch_pool));
+  SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+            &incoming_new_repos_relpath, &incoming_new_pegrev,
+            &incoming_new_kind, conflict, scratch_pool,
+            scratch_pool));
+
+  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;
+      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;
+      if (details != NULL && details->wc_siblings != NULL)
+        {
+          const char *description;
+          const char *sibling;
+
+          sibling =
+            apr_pstrdup(conflict->pool,
+                        APR_ARRAY_IDX(details->wc_siblings,
+                                      details->preferred_sibling_idx,
+                                      const char *));
+          description =
+            apr_psprintf(
+              scratch_pool, _("apply changes to '%s'"),
+              svn_dirent_local_style(
+                svn_dirent_skip_ancestor(wcroot_abspath, sibling),
+                scratch_pool));
+
+          if ((incoming_old_kind == svn_node_file ||
+               incoming_old_kind == svn_node_none) &&
+              (incoming_new_kind == svn_node_file ||
+               incoming_new_kind == svn_node_none))
+            {
+              add_resolution_option(
+                options, conflict,
+                svn_client_conflict_option_sibling_move_file_text_merge,
+                _("Apply to corresponding local location"),
+                description, resolve_local_move_file_merge);
+            }
+          else
+            {
+              add_resolution_option(
+                options, conflict,
+                svn_client_conflict_option_sibling_move_dir_merge,
+                _("Apply to corresponding local location"),
+                description, resolve_local_move_dir_merge);
+            }
+        }
+    }
+
+  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 uncomitted 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;
+
+      /* 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);
+    }
+
+  /* Record a new move which overrides the incoming move. */
+  err = svn_wc__move2(ctx->wc_ctx, incoming_moved_to_abspath,
+                      local_moved_to_abspath,
+                      TRUE, /* meta-data only move */
+                      FALSE, /* mixed-revisions don't apply to files */
+                      NULL, NULL, /* don't allow user to cancel here */
+                      NULL, NULL, /* no extra notification */
+                      scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  /* Remove moved-away file from disk. */
+  err = svn_io_remove_file2(incoming_moved_to_abspath, TRUE, 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

[... 1005 lines stripped ...]

Reply via email to