subversion-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From s...@apache.org
Subject svn commit: r1846851 - in /subversion/trunk/subversion: include/svn_client.h libsvn_client/conflicts.c libsvn_wc/conflicts.c svn/conflict-callbacks.c
Date Sun, 18 Nov 2018 16:42:14 GMT
Author: stsp
Date: Sun Nov 18 16:42:14 2018
New Revision: 1846851

URL: http://svn.apache.org/viewvc?rev=1846851&view=rev
Log:
Add resolver support for incoming move vs local move of files during merges.

Add two new resolution options for move vs move conflicts with files
after a merge operation:

  (m)  - apply changes to 'file-target' and revert addition of 'file-source'
  (M)  - move 'file-target' to 'file-source' and merge  

where 'file-branch' is the path the file has on the merge target branch,
and 'file-trunk' is the path the file has on the merge source branch.

The 'm' option applies textual changes only and leaves the tree structure
of the merge target as it is. The 'M' option changes the file's location in
the merge target to match that of the merge source before merging changes.

I have not written related tests yet, but some manual testing was done.
Proper tests will follow as soon.

* subversion/include/svn_client.h
  (svn_client_conflict_option_both_moved_file_merge,
   svn_client_conflict_option_both_moved_file_move_merge): Declare new options.

* subversion/libsvn_client/conflicts.c
  (follow_move_chains): Check for local candidates along all moves in the
   chain, not just for the final move in the chain.
  (resolve_incoming_move_file_text_merge): Implement support for the
   svn_client_conflict_option_both_moved_file_move_merge option. This case
   is similar enough to make sharing code written for other cases possible.
  (resolve_both_moved_file_text_merge): New resolution handler for the
   svn_client_conflict_option_both_moved_file_merge option.
  (configure_option_incoming_delete_ignore,
   configure_option_incoming_delete_accept): Don't match move-vs-move cases.
  (describe_incoming_move_merge_conflict_option): Change 'details' parameter
   to a move target path, so this function can also be used for the new
   svn_client_conflict_option_both_moved_file_move_merge option.
  (configure_option_incoming_move_file_merge,
  configure_option_incoming_dir_merge): Update callers.
  (configure_option_both_moved_file_merge): Configure new options.
  (svn_client_conflict_option_get_moved_to_repos_relpaths,
   svn_client_conflict_option_set_moved_to_repos_relpaths
   svn_client_conflict_option_get_moved_to_abspath_candidates,
   svn_client_conflict_option_set_moved_to_abspath2): Accept new options and
    update calls to describe_incoming_move_merge_conflict_option().
  (svn_client_conflict_tree_get_resolution_options): Enable new options.

* subversion/libsvn_wc/conflicts.c
  (svn_wc__guess_incoming_move_target_nodes): Handle the case where the
   conflict victim is a 'local missing' victim. Needed for new options.

* subversion/svn/conflict-callbacks.c
 (builtin_resolver_options): Set menu key bindings for new options.

Modified:
    subversion/trunk/subversion/include/svn_client.h
    subversion/trunk/subversion/libsvn_client/conflicts.c
    subversion/trunk/subversion/libsvn_wc/conflicts.c
    subversion/trunk/subversion/svn/conflict-callbacks.c

Modified: subversion/trunk/subversion/include/svn_client.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/include/svn_client.h?rev=1846851&r1=1846850&r2=1846851&view=diff
==============================================================================
--- subversion/trunk/subversion/include/svn_client.h (original)
+++ subversion/trunk/subversion/include/svn_client.h Sun Nov 18 16:42:14 2018
@@ -4565,6 +4565,9 @@ typedef enum svn_client_conflict_option_
   svn_client_conflict_option_sibling_move_file_text_merge, /**< @since New in 1.11. */
   svn_client_conflict_option_sibling_move_dir_merge, /**< @since New in 1.11. */
 
+  /* Options for local move vs incoming move on merge. */
+  svn_client_conflict_option_both_moved_file_merge, /*< since New in 1.12 */
+  svn_client_conflict_option_both_moved_file_move_merge, /*< since New in 1.12 */
 } svn_client_conflict_option_id_t;
 
 /**

Modified: subversion/trunk/subversion/libsvn_client/conflicts.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/conflicts.c?rev=1846851&r1=1846850&r2=1846851&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/conflicts.c (original)
+++ subversion/trunk/subversion/libsvn_client/conflicts.c Sun Nov 18 16:42:14 2018
@@ -2675,91 +2675,88 @@ follow_move_chains(apr_hash_t *wc_move_t
                    apr_pool_t *result_pool,
                    apr_pool_t *scratch_pool)
 {
-  /* If this is the end of a move chain, look for matching paths in
-   * the working copy and add them to our collection if found. */
-  if (move->next == NULL)
-    {
-      apr_array_header_t *candidate_abspaths;
-
-      /* Gather candidate nodes which represent this moved_to_repos_relpath. */
-      SVN_ERR(svn_wc__guess_incoming_move_target_nodes(
-                &candidate_abspaths, ctx->wc_ctx,
-                victim_abspath, victim_node_kind,
-                move->moved_to_repos_relpath,
-                scratch_pool, scratch_pool));
-      if (candidate_abspaths->nelts > 0)
-        {
-          apr_array_header_t *moved_to_abspaths;
-          int i;
-          apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+  apr_array_header_t *candidate_abspaths;
 
-          moved_to_abspaths = apr_array_make(result_pool, 1,
-                                             sizeof (const char *));
+  /* Gather candidate nodes which represent this moved_to_repos_relpath. */
+  SVN_ERR(svn_wc__guess_incoming_move_target_nodes(
+            &candidate_abspaths, ctx->wc_ctx,
+            victim_abspath, victim_node_kind,
+            move->moved_to_repos_relpath,
+            scratch_pool, scratch_pool));
 
-          for (i = 0; i < candidate_abspaths->nelts; i++)
-            {
-              const char *candidate_abspath;
-              const char *repos_root_url;
-              const char *repos_uuid;
-              const char *candidate_repos_relpath;
-              svn_revnum_t candidate_revision;
+  if (candidate_abspaths->nelts > 0)
+    {
+      apr_array_header_t *moved_to_abspaths;
+      int i;
+      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
 
-              svn_pool_clear(iterpool);
+      moved_to_abspaths = apr_array_make(result_pool, 1,
+                                         sizeof (const char *));
 
-              candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i,
-                                                const char *);
-              SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision,
-                                              &candidate_repos_relpath,
-                                              &repos_root_url,
-                                              &repos_uuid,
-                                              NULL, NULL,
-                                              ctx->wc_ctx,
-                                              candidate_abspath,
-                                              FALSE,
-                                              iterpool, iterpool));
+      for (i = 0; i < candidate_abspaths->nelts; i++)
+        {
+          const char *candidate_abspath;
+          const char *repos_root_url;
+          const char *repos_uuid;
+          const char *candidate_repos_relpath;
+          svn_revnum_t candidate_revision;
 
-              if (candidate_revision == SVN_INVALID_REVNUM)
-                continue;
+          svn_pool_clear(iterpool);
 
-              /* If the conflict victim and the move target candidate
-               * are not from the same revision we must ensure that
-               * they are related. */
-               if (candidate_revision != victim_revision)
-                {
-                  svn_client__pathrev_t *yca_loc;
-                  svn_error_t *err;
+          candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i,
+                                            const char *);
+          SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision,
+                                          &candidate_repos_relpath,
+                                          &repos_root_url,
+                                          &repos_uuid,
+                                          NULL, NULL,
+                                          ctx->wc_ctx,
+                                          candidate_abspath,
+                                          FALSE,
+                                          iterpool, iterpool));
+
+          if (candidate_revision == SVN_INVALID_REVNUM)
+            continue;
+
+          /* If the conflict victim and the move target candidate
+           * are not from the same revision we must ensure that
+           * they are related. */
+           if (candidate_revision != victim_revision)
+            {
+              svn_client__pathrev_t *yca_loc;
+              svn_error_t *err;
 
-                  err = find_yca(&yca_loc, victim_repos_relpath,
-                                 victim_revision,
-                                 candidate_repos_relpath,
-                                 candidate_revision,
-                                 repos_root_url, repos_uuid,
-                                 NULL, ctx, iterpool, iterpool);
-                  if (err)
+              err = find_yca(&yca_loc, victim_repos_relpath,
+                             victim_revision,
+                             candidate_repos_relpath,
+                             candidate_revision,
+                             repos_root_url, repos_uuid,
+                             NULL, ctx, iterpool, iterpool);
+              if (err)
+                {
+                  if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
                     {
-                      if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
-                        {
-                          svn_error_clear(err);
-                          yca_loc = NULL;
-                        }
-                      else
-                        return svn_error_trace(err);
+                      svn_error_clear(err);
+                      yca_loc = NULL;
                     }
-
-                  if (yca_loc == NULL)
-                    continue;
+                  else
+                    return svn_error_trace(err);
                 }
 
-              APR_ARRAY_PUSH(moved_to_abspaths, const char *) =
-                apr_pstrdup(result_pool, candidate_abspath);
+              if (yca_loc == NULL)
+                continue;
             }
-          svn_pool_destroy(iterpool);
 
-          svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath,
-                        moved_to_abspaths);
+          APR_ARRAY_PUSH(moved_to_abspaths, const char *) =
+            apr_pstrdup(result_pool, candidate_abspath);
         }
+      svn_pool_destroy(iterpool);
+
+      svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath,
+                    moved_to_abspaths);
     }
-  else
+
+  if (move->next)
     {
       int i;
       apr_pool_t *iterpool;
@@ -8491,7 +8488,9 @@ resolve_incoming_move_file_text_merge(sv
                                       apr_pool_t *scratch_pool)
 {
   svn_client_conflict_option_id_t option_id;
-  const char *local_abspath;
+  const char *victim_abspath;
+  const char *merge_source_abspath;
+  svn_wc_conflict_reason_t local_change;
   svn_wc_operation_t operation;
   const char *lock_abspath;
   svn_error_t *err;
@@ -8517,7 +8516,8 @@ resolve_incoming_move_file_text_merge(sv
   const char *moved_to_abspath;
   const char *incoming_abspath = NULL;
 
-  local_abspath = svn_client_conflict_get_local_abspath(conflict);
+  victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+  local_change = svn_client_conflict_get_local_change(conflict);
   operation = svn_client_conflict_get_operation(conflict);
   details = conflict->tree_conflict_incoming_details;
   if (details == NULL || details->moves == NULL)
@@ -8525,20 +8525,20 @@ resolve_incoming_move_file_text_merge(sv
                              _("The specified conflict resolution option "
                                "requires details for tree conflict at '%s' "
                                "to be fetched from the repository first."),
-                            svn_dirent_local_style(local_abspath,
+                            svn_dirent_local_style(victim_abspath,
                                                    scratch_pool));
   if (operation == svn_wc_operation_none)
     return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
                              _("Invalid operation code '%d' recorded for "
                                "conflict at '%s'"), operation,
-                             svn_dirent_local_style(local_abspath,
+                             svn_dirent_local_style(victim_abspath,
                                                     scratch_pool));
 
   option_id = svn_client_conflict_option_get_id(option);
   SVN_ERR_ASSERT(option_id ==
                  svn_client_conflict_option_incoming_move_file_text_merge ||
                  option_id ==
-                 svn_client_conflict_option_incoming_move_dir_merge);
+                 svn_client_conflict_option_both_moved_file_move_merge);
                   
   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
                                              conflict, scratch_pool,
@@ -8553,7 +8553,7 @@ resolve_incoming_move_file_text_merge(sv
             scratch_pool));
 
   /* Set up temporary storage for the common ancestor version of the file. */
-  SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
+  SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath,
                              scratch_pool, scratch_pool));
   SVN_ERR(svn_stream_open_unique(&ancestor_stream,
                                  &ancestor_abspath, wc_tmpdir,
@@ -8583,21 +8583,41 @@ resolve_incoming_move_file_text_merge(sv
                                    details->wc_move_target_idx,
                                    const char *);
 
+  if (local_change == svn_wc_conflict_reason_missing)
+    {
+      /* This is an incoming move vs local move conflict.
+       * Merge from the local move's target location to the
+       * incoming move's target location. */
+      struct conflict_tree_local_missing_details *local_details;
+      apr_array_header_t *moves;
+
+      local_details = conflict->tree_conflict_local_details;
+      moves = svn_hash_gets(local_details->wc_move_targets,
+                            local_details->move_target_repos_relpath);
+      merge_source_abspath =
+        APR_ARRAY_IDX(moves, local_details->wc_move_target_idx, const char *);
+    }
+  else
+    merge_source_abspath = victim_abspath;
+
   /* ### The following WC modifications should be atomic. */
   SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
             &lock_abspath, ctx->wc_ctx,
-            svn_dirent_get_longest_ancestor(local_abspath,
+            svn_dirent_get_longest_ancestor(victim_abspath,
                                             moved_to_abspath,
                                             scratch_pool),
             scratch_pool, scratch_pool));
 
-  err = verify_local_state_for_incoming_delete(conflict, option, ctx,
-                                               scratch_pool);
-  if (err)
-    goto unlock_wc;
+  if (local_change != svn_wc_conflict_reason_missing)
+    {
+      err = verify_local_state_for_incoming_delete(conflict, option, ctx,
+                                                   scratch_pool);
+      if (err)
+        goto unlock_wc;
+    }
 
    /* Get a copy of the conflict victim's properties. */
-  err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, local_abspath,
+  err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, merge_source_abspath,
                           scratch_pool, scratch_pool);
   if (err)
     goto unlock_wc;
@@ -8631,7 +8651,8 @@ resolve_incoming_move_file_text_merge(sv
         goto unlock_wc;
 
       err = svn_wc__translated_stream(&working_stream, ctx->wc_ctx,
-                                      local_abspath, local_abspath,
+                                      merge_source_abspath,
+                                      merge_source_abspath,
                                       SVN_WC_TRANSLATE_TO_NF,
                                       scratch_pool, scratch_pool);
       if (err)
@@ -8678,7 +8699,7 @@ resolve_incoming_move_file_text_merge(sv
       err = svn_io_remove_file2(moved_to_abspath, FALSE, scratch_pool);
       if (err)
         goto unlock_wc;
-      err = svn_wc__move2(ctx->wc_ctx, local_abspath, moved_to_abspath,
+      err = svn_wc__move2(ctx->wc_ctx, merge_source_abspath, moved_to_abspath,
                           FALSE, /* ordinary (not meta-data only) move */
                           FALSE, /* mixed-revisions don't apply to files */
                           NULL, NULL, /* don't allow user to cancel here */
@@ -8736,7 +8757,7 @@ resolve_incoming_move_file_text_merge(sv
       operation == svn_wc_operation_switch)
     {
       /* Delete the tree conflict victim (clears the tree conflict marker). */
-      err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
+      err = svn_wc_delete4(ctx->wc_ctx, victim_abspath, FALSE, FALSE,
                            NULL, NULL, /* don't allow user to cancel here */
                            NULL, NULL, /* no extra notification */
                            scratch_pool);
@@ -8748,7 +8769,7 @@ resolve_incoming_move_file_text_merge(sv
     {
       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);
     }
@@ -8769,6 +8790,213 @@ unlock_wc:
   return SVN_NO_ERROR;
 }
 
+/* Implements conflict_option_resolve_func_t.
+ * Resolve an incoming move vs local move conflict by merging from the
+ * incoming move's target location to the local move's target location,
+ * overriding the incoming move. */
+static svn_error_t *
+resolve_both_moved_file_text_merge(svn_client_conflict_option_t *option,
+                                   svn_client_conflict_t *conflict,
+                                   svn_client_ctx_t *ctx,
+                                   apr_pool_t *scratch_pool)
+{
+  svn_client_conflict_option_id_t option_id;
+  const char *victim_abspath;
+  const char *local_moved_to_abspath;
+  svn_wc_conflict_reason_t local_change;
+  svn_wc_operation_t operation;
+  const char *lock_abspath;
+  svn_error_t *err;
+  const char *repos_root_url;
+  const char *incoming_old_repos_relpath;
+  svn_revnum_t incoming_old_pegrev;
+  const char *incoming_new_repos_relpath;
+  svn_revnum_t incoming_new_pegrev;
+  const char *wc_tmpdir;
+  const char *ancestor_abspath;
+  svn_stream_t *ancestor_stream;
+  apr_hash_t *ancestor_props;
+  apr_hash_t *incoming_props;
+  apr_hash_t *local_props;
+  const char *ancestor_url;
+  const char *corrected_url;
+  svn_ra_session_t *ra_session;
+  svn_wc_merge_outcome_t merge_content_outcome;
+  svn_wc_notify_state_t merge_props_outcome;
+  apr_array_header_t *propdiffs;
+  struct conflict_tree_incoming_delete_details *incoming_details;
+  apr_array_header_t *possible_moved_to_abspaths;
+  const char *incoming_moved_to_abspath;
+  struct conflict_tree_local_missing_details *local_details;
+  apr_array_header_t *local_moves;
+
+  victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+  local_change = svn_client_conflict_get_local_change(conflict);
+  operation = svn_client_conflict_get_operation(conflict);
+  incoming_details = conflict->tree_conflict_incoming_details;
+  if (incoming_details == NULL || incoming_details->moves == NULL)
+    return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+                             _("The specified conflict resolution option "
+                               "requires details for tree conflict at '%s' "
+                               "to be fetched from the repository first."),
+                            svn_dirent_local_style(victim_abspath,
+                                                   scratch_pool));
+  if (operation == svn_wc_operation_none)
+    return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+                             _("Invalid operation code '%d' recorded for "
+                               "conflict at '%s'"), operation,
+                             svn_dirent_local_style(victim_abspath,
+                                                    scratch_pool));
+
+  option_id = svn_client_conflict_option_get_id(option);
+  SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_file_merge);
+                  
+  SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
+                                             conflict, scratch_pool,
+                                             scratch_pool));
+  SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+            &incoming_old_repos_relpath, &incoming_old_pegrev,
+            NULL, conflict, scratch_pool,
+            scratch_pool));
+  SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+            &incoming_new_repos_relpath, &incoming_new_pegrev,
+            NULL, conflict, scratch_pool,
+            scratch_pool));
+
+  /* Set up temporary storage for the common ancestor version of the file. */
+  SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath,
+                             scratch_pool, scratch_pool));
+  SVN_ERR(svn_stream_open_unique(&ancestor_stream,
+                                 &ancestor_abspath, wc_tmpdir,
+                                 svn_io_file_del_on_pool_cleanup,
+                                 scratch_pool, scratch_pool));
+
+  /* Fetch the ancestor file's content. */
+  ancestor_url = svn_path_url_add_component2(repos_root_url,
+                                             incoming_old_repos_relpath,
+                                             scratch_pool);
+  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
+                                               ancestor_url, NULL, NULL,
+                                               FALSE, FALSE, ctx,
+                                               scratch_pool, scratch_pool));
+  SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev,
+                          ancestor_stream, NULL, /* fetched_rev */
+                          &ancestor_props, scratch_pool));
+  filter_props(ancestor_props, scratch_pool);
+
+  /* Close stream to flush ancestor file to disk. */
+  SVN_ERR(svn_stream_close(ancestor_stream));
+
+  possible_moved_to_abspaths =
+    svn_hash_gets(incoming_details->wc_move_targets,
+                  get_moved_to_repos_relpath(incoming_details, scratch_pool));
+  incoming_moved_to_abspath =
+    APR_ARRAY_IDX(possible_moved_to_abspaths,
+                  incoming_details->wc_move_target_idx, const char *);
+
+  local_details = conflict->tree_conflict_local_details;
+  local_moves = svn_hash_gets(local_details->wc_move_targets,
+                        local_details->move_target_repos_relpath);
+  local_moved_to_abspath =
+    APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *);
+
+  /* ### The following WC modifications should be atomic. */
+  SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
+            &lock_abspath, ctx->wc_ctx,
+            svn_dirent_get_longest_ancestor(victim_abspath,
+                                            local_moved_to_abspath,
+                                            scratch_pool),
+            scratch_pool, scratch_pool));
+
+   /* Get a copy of the incoming moved item's properties. */
+  err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx,
+                          incoming_moved_to_abspath,
+                          scratch_pool, scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  /* Get a copy of the local move target's properties. */
+  err = svn_wc_prop_list2(&local_props, ctx->wc_ctx,
+                          local_moved_to_abspath,
+                          scratch_pool, scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  /* Create a property diff for the files. */
+  err = svn_prop_diffs(&propdiffs, incoming_props, local_props,
+                       scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  /* Perform the file merge. */
+  err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
+                      ctx->wc_ctx, ancestor_abspath,
+                      incoming_moved_to_abspath, local_moved_to_abspath,
+                      NULL, NULL, NULL, /* labels */
+                      NULL, NULL, /* conflict versions */
+                      FALSE, /* dry run */
+                      NULL, NULL, /* diff3_cmd, merge_options */
+                      apr_hash_count(ancestor_props) ? ancestor_props : NULL,
+                      propdiffs,
+                      NULL, NULL, /* conflict func/baton */
+                      NULL, NULL, /* don't allow user to cancel here */
+                      scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  if (ctx->notify_func2)
+    {
+      svn_wc_notify_t *notify;
+
+      /* Tell the world about the file merge that just happened. */
+      notify = svn_wc_create_notify(local_moved_to_abspath,
+                                    svn_wc_notify_update_update,
+                                    scratch_pool);
+      if (merge_content_outcome == svn_wc_merge_conflict)
+        notify->content_state = svn_wc_notify_state_conflicted;
+      else
+        notify->content_state = svn_wc_notify_state_merged;
+      notify->prop_state = merge_props_outcome;
+      notify->kind = svn_node_file;
+      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+    }
+
+  /* Revert local addition of the incoming move's target. */
+  err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath,
+                       svn_depth_infinity, FALSE, NULL, TRUE, FALSE,
+                       FALSE /*added_keep_local*/,
+                       NULL, NULL, /* no cancellation */
+                       ctx->notify_func2, ctx->notify_baton2,
+                       scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  if (ctx->notify_func2)
+    {
+      svn_wc_notify_t *notify;
+
+      notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
+                                    scratch_pool);
+      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+    }
+
+  svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
+
+  conflict->resolution_tree = option_id;
+
+unlock_wc:
+  err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
+                                                                 lock_abspath,
+                                                                 scratch_pool));
+  SVN_ERR(err);
+
+  return SVN_NO_ERROR;
+}
+
 /* Implements conflict_option_resolve_func_t. */
 static svn_error_t *
 resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option,
@@ -10075,7 +10303,7 @@ configure_option_incoming_delete_ignore(
           is_local_move = (local_details != NULL &&
                            local_details->moves != NULL);
 
-          if (!is_incoming_move && !is_local_move)
+          if (is_incoming_move || is_local_move)
             return SVN_NO_ERROR;
         }
 
@@ -10121,7 +10349,8 @@ configure_option_incoming_delete_accept(
                           incoming_details->moves != NULL);
       if (is_incoming_move &&
           (local_change == svn_wc_conflict_reason_edited ||
-          local_change == svn_wc_conflict_reason_moved_away))
+          local_change == svn_wc_conflict_reason_moved_away ||
+          local_change == svn_wc_conflict_reason_missing))
         {
           /* An option which accepts the incoming deletion makes no sense
            * if we know there was a local move and/or an incoming move. */
@@ -10158,39 +10387,75 @@ describe_incoming_move_merge_conflict_op
   const char **description,
   svn_client_conflict_t *conflict,
   svn_client_ctx_t *ctx,
-  struct conflict_tree_incoming_delete_details *details,
+  const char *moved_to_abspath,
   apr_pool_t *result_pool,
   apr_pool_t *scratch_pool)
 {
-  apr_array_header_t *move_target_wc_abspaths;
   svn_wc_operation_t operation;
   const char *victim_abspath;
-  const char *moved_to_abspath;
+  svn_node_kind_t victim_node_kind;
   const char *wcroot_abspath;
 
-  move_target_wc_abspaths =
-    svn_hash_gets(details->wc_move_targets,
-                  get_moved_to_repos_relpath(details, scratch_pool));
-  moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
-                                   details->wc_move_target_idx,
-                                   const char *);
-
   victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+  victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
   SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
                              victim_abspath, scratch_pool,
                              scratch_pool));
 
   operation = svn_client_conflict_get_operation(conflict);
   if (operation == svn_wc_operation_merge)
-    *description =
-      apr_psprintf(
-        result_pool, _("move '%s' to '%s' and merge"),
-        svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
-                                                        victim_abspath),
-                               scratch_pool),
-        svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
-                                                        moved_to_abspath),
-                               scratch_pool));
+    {
+      const char *incoming_moved_abspath = NULL;
+
+      if (victim_node_kind == svn_node_none)
+        {
+          /* This is an incoming move vs local move conflict. */
+          struct conflict_tree_incoming_delete_details *details;
+
+          details = conflict->tree_conflict_incoming_details;
+          if (details->wc_move_targets)
+            {
+              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(
@@ -10221,6 +10486,7 @@ configure_option_incoming_move_file_merg
   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);
   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
@@ -10239,6 +10505,8 @@ configure_option_incoming_move_file_merg
     {
       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)
@@ -10247,9 +10515,15 @@ configure_option_incoming_move_file_merg
       if (apr_hash_count(details->wc_move_targets) == 0)
         return SVN_NO_ERROR;
 
+      move_target_wc_abspaths =
+        svn_hash_gets(details->wc_move_targets,
+                      get_moved_to_repos_relpath(details, scratch_pool));
+      moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
+                                       details->wc_move_target_idx,
+                                       const char *);
       SVN_ERR(describe_incoming_move_merge_conflict_option(&description,
                                                            conflict, ctx,
-                                                           details,
+                                                           moved_to_abspath,
                                                            scratch_pool,
                                                            scratch_pool));
       add_resolution_option(
@@ -10300,6 +10574,8 @@ configure_option_incoming_dir_merge(svn_
     {
       struct conflict_tree_incoming_delete_details *details;
       const char *description;
+      apr_array_header_t *move_target_wc_abspaths;
+      const char *moved_to_abspath;
 
       details = conflict->tree_conflict_incoming_details;
       if (details == NULL || details->moves == NULL)
@@ -10308,9 +10584,15 @@ configure_option_incoming_dir_merge(svn_
       if (apr_hash_count(details->wc_move_targets) == 0)
         return SVN_NO_ERROR;
 
+      move_target_wc_abspaths =
+        svn_hash_gets(details->wc_move_targets,
+                      get_moved_to_repos_relpath(details, scratch_pool));
+      moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
+                                       details->wc_move_target_idx,
+                                       const char *);
       SVN_ERR(describe_incoming_move_merge_conflict_option(&description,
                                                            conflict, ctx,
-                                                           details,
+                                                           moved_to_abspath,
                                                            scratch_pool,
                                                            scratch_pool));
       add_resolution_option(options, conflict,
@@ -10493,6 +10775,107 @@ configure_option_sibling_move_merge(svn_
   return SVN_NO_ERROR;
 }
 
+/* Configure 'both moved file merge' resolution options for a tree conflict. */
+static svn_error_t *
+configure_option_both_moved_file_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_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;
+  const char *wcroot_abspath;
+
+  SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+                             conflict->local_abspath, scratch_pool,
+                             scratch_pool));
+
+  operation = svn_client_conflict_get_operation(conflict);
+  victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(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 &&
+      victim_node_kind == svn_node_none &&
+      incoming_old_kind == svn_node_file &&
+      incoming_new_kind == svn_node_none &&
+      local_change == svn_wc_conflict_reason_missing &&
+      incoming_change == svn_wc_conflict_action_delete)
+    {
+      struct conflict_tree_incoming_delete_details *incoming_details;
+      struct conflict_tree_local_missing_details *local_details;
+      const char *description;
+      apr_array_header_t *local_moves;
+      const char *local_moved_to_abspath;
+      const char *incoming_moved_to_abspath;
+      apr_array_header_t *incoming_move_target_wc_abspaths;
+
+      incoming_details = conflict->tree_conflict_incoming_details;
+      if (incoming_details == NULL || incoming_details->moves == NULL ||
+          apr_hash_count(incoming_details->wc_move_targets) == 0)
+              return SVN_NO_ERROR;
+
+      local_details = conflict->tree_conflict_local_details;
+      if (local_details == NULL ||
+          apr_hash_count(local_details->wc_move_targets) == 0)
+          return SVN_NO_ERROR;
+
+      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 *);
+
+      incoming_move_target_wc_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(incoming_move_target_wc_abspaths,
+                      incoming_details->wc_move_target_idx, const char *);
+
+      description =
+        apr_psprintf(
+          scratch_pool, _("apply changes to '%s' and revert addition of '%s'"),
+          svn_dirent_local_style(
+            svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath),
+            scratch_pool),
+          svn_dirent_local_style(
+            svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
+            scratch_pool));
+      add_resolution_option(
+        options, conflict, svn_client_conflict_option_both_moved_file_merge,
+        _("Merge to corresponding local location"),
+        description, resolve_both_moved_file_text_merge);
+
+      SVN_ERR(describe_incoming_move_merge_conflict_option(
+                &description, conflict, ctx, local_moved_to_abspath,
+                scratch_pool, scratch_pool));
+      add_resolution_option(options, conflict,
+         svn_client_conflict_option_both_moved_file_move_merge,
+        _("Move and merge"), description,
+        resolve_incoming_move_file_text_merge);
+    }
+
+  return SVN_NO_ERROR;
+}
+
 /* Return a copy of the repos replath candidate list. */
 static svn_error_t *
 get_repos_relpath_candidates(
@@ -10545,7 +10928,9 @@ svn_client_conflict_option_get_moved_to_
       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)
+      id != svn_client_conflict_option_sibling_move_dir_merge &&
+      id != svn_client_conflict_option_both_moved_file_merge &&
+      id != svn_client_conflict_option_both_moved_file_move_merge)
     {
       /* We cannot operate on this option. */
       *possible_moved_to_repos_relpaths = NULL;
@@ -10687,7 +11072,9 @@ svn_client_conflict_option_set_moved_to_
       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)
+      id != svn_client_conflict_option_sibling_move_dir_merge &&
+      id != svn_client_conflict_option_both_moved_file_merge &&
+      id != svn_client_conflict_option_both_moved_file_move_merge)
     return SVN_NO_ERROR; /* We cannot operate on this option. Nothing to do. */
 
   victim_abspath = svn_client_conflict_get_local_abspath(conflict);
@@ -10724,6 +11111,8 @@ svn_client_conflict_option_set_moved_to_
   else
     {
       struct conflict_tree_incoming_delete_details *details;
+      apr_array_header_t *move_target_wc_abspaths;
+      const char *moved_to_abspath;
 
       details = conflict->tree_conflict_incoming_details;
       if (details == NULL || details->wc_move_targets == NULL)
@@ -10741,10 +11130,16 @@ svn_client_conflict_option_set_moved_to_
       details->wc_move_target_idx = 0;
 
       /* Update option description. */
+      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(
                 &option->description,
                 conflict, ctx,
-                details,
+                moved_to_abspath,
                 conflict->pool,
                 scratch_pool));
     }
@@ -10789,7 +11184,9 @@ svn_client_conflict_option_get_moved_to_
       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)
+      id != svn_client_conflict_option_sibling_move_dir_merge &&
+      id != svn_client_conflict_option_both_moved_file_merge &&
+      id != svn_client_conflict_option_both_moved_file_move_merge)
     {
       /* We cannot operate on this option. */
       *possible_moved_to_abspaths = NULL;
@@ -10923,7 +11320,9 @@ svn_client_conflict_option_set_moved_to_
       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)
+      id != svn_client_conflict_option_sibling_move_dir_merge &&
+      id != svn_client_conflict_option_both_moved_file_merge &&
+      id != svn_client_conflict_option_both_moved_file_move_merge)
     return NULL; /* We cannot operate on this option. Nothing to do. */
 
   victim_abspath = svn_client_conflict_get_local_abspath(conflict);
@@ -11008,6 +11407,7 @@ svn_client_conflict_option_set_moved_to_
     {
       struct conflict_tree_incoming_delete_details *details;
       apr_array_header_t *move_target_wc_abspaths;
+      const char *moved_to_abspath;
 
       details = conflict->tree_conflict_incoming_details;
       if (details == NULL || details->wc_move_targets == NULL)
@@ -11035,9 +11435,12 @@ svn_client_conflict_option_set_moved_to_
       details->wc_move_target_idx = preferred_move_target_idx;
 
       /* Update option description. */
+      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(&option->description,
                                                            conflict, ctx,
-                                                           details,
+                                                           moved_to_abspath,
                                                            conflict->pool,
                                                            scratch_pool));
     }
@@ -11118,6 +11521,8 @@ svn_client_conflict_tree_get_resolution_
                                                         scratch_pool));
   SVN_ERR(configure_option_sibling_move_merge(conflict, ctx, *options,
                                               scratch_pool));
+  SVN_ERR(configure_option_both_moved_file_merge(conflict, ctx, *options,
+                                                 scratch_pool));
 
   return SVN_NO_ERROR;
 }

Modified: subversion/trunk/subversion/libsvn_wc/conflicts.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_wc/conflicts.c?rev=1846851&r1=1846850&r2=1846851&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_wc/conflicts.c (original)
+++ subversion/trunk/subversion/libsvn_wc/conflicts.c Sun Nov 18 16:42:14 2018
@@ -3938,7 +3938,7 @@ svn_wc__guess_incoming_move_target_nodes
           status != svn_wc__db_status_added)
         continue;
 
-      if (node_kind != victim_node_kind)
+      if (victim_node_kind != svn_node_none && node_kind != victim_node_kind)
         continue;
 
       SVN_ERR(svn_wc__db_is_switched(&is_wcroot, &is_switched, NULL,

Modified: subversion/trunk/subversion/svn/conflict-callbacks.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/svn/conflict-callbacks.c?rev=1846851&r1=1846850&r2=1846851&view=diff
==============================================================================
--- subversion/trunk/subversion/svn/conflict-callbacks.c (original)
+++ subversion/trunk/subversion/svn/conflict-callbacks.c Sun Nov 18 16:42:14 2018
@@ -446,6 +446,10 @@ static const resolver_option_t builtin_r
   { "m", svn_client_conflict_option_sibling_move_file_text_merge },
   { "m", svn_client_conflict_option_sibling_move_dir_merge },
 
+  /* Options for incoming move vs local move. */
+  { "m", svn_client_conflict_option_both_moved_file_merge },
+  { "M", svn_client_conflict_option_both_moved_file_move_merge },
+
   { NULL }
 };
 



Mime
View raw message