subversion-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rhuij...@apache.org
Subject svn commit: r1773992 [2/8] - in /subversion/branches/ra-git: ./ build/ac-macros/ notes/ subversion/bindings/javahl/native/ subversion/bindings/swig/perl/libsvn_swig_perl/ subversion/bindings/swig/ruby/test/ subversion/include/ subversion/include/privat...
Date Tue, 13 Dec 2016 12:36:57 GMT
Modified: subversion/branches/ra-git/subversion/libsvn_client/conflicts.c
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/subversion/libsvn_client/conflicts.c?rev=1773992&r1=1773991&r2=1773992&view=diff
==============================================================================
--- subversion/branches/ra-git/subversion/libsvn_client/conflicts.c (original)
+++ subversion/branches/ra-git/subversion/libsvn_client/conflicts.c Tue Dec 13 12:36:55 2016
@@ -37,7 +37,11 @@
 #include "svn_props.h"
 #include "svn_hash.h"
 #include "svn_sorts.h"
+#include "svn_subst.h"
 #include "client.h"
+
+#include "private/svn_diff_tree.h"
+#include "private/svn_ra_private.h"
 #include "private/svn_sorts_private.h"
 #include "private/svn_token.h"
 #include "private/svn_wc_private.h"
@@ -70,7 +74,7 @@ struct svn_client_conflict_t
   apr_hash_t *prop_conflicts;
 
   /* Indicate which options were chosen to resolve a text or tree conflict
-   * on the conflited node. */
+   * on the conflicted node. */
   svn_client_conflict_option_id_t resolution_text;
   svn_client_conflict_option_id_t resolution_tree;
 
@@ -119,6 +123,7 @@ typedef svn_error_t *(*conflict_option_r
 struct svn_client_conflict_option_t
 {
   svn_client_conflict_option_id_t id;
+  const char *label;
   const char *description;
 
   svn_client_conflict_t *conflict;
@@ -273,29 +278,36 @@ struct repos_move_info {
   apr_array_header_t *next;
 };
 
+static svn_revnum_t
+rev_below(svn_revnum_t rev)
+{
+  SVN_ERR_ASSERT_NO_RETURN(rev != SVN_INVALID_REVNUM);
+  SVN_ERR_ASSERT_NO_RETURN(rev > 0);
+
+  return rev == 1 ? 1 : rev - 1;
+}
+
 /* Set *RELATED to true if the deleted node DELETED_REPOS_RELPATH@DELETED_REV
- * is an ancestor of the copied node COPYFROM_PATH@COPYFROM_REV, and if the
- * copied node is a copy of the deleted node's last-changed revision's content,
- * rather than a copy of some older content. */
+ * is an ancestor of the copied node COPYFROM_PATH@COPYFROM_REV.
+ * If CHECK_LAST_CHANGED_REV is non-zero, also ensure that the copied node
+ * is a copy of the deleted node's last-changed revision's content, rather
+ * than a copy of some older content. If it's not, set *RELATED to false. */
 static svn_error_t *
 check_move_ancestry(svn_boolean_t *related,
+                    svn_ra_session_t *ra_session,
                     const char *repos_root_url,
                     const char *deleted_repos_relpath,
                     svn_revnum_t deleted_rev,
                     const char *copyfrom_path,
                     svn_revnum_t copyfrom_rev,
-                    svn_client_ctx_t *ctx,
+                    svn_boolean_t check_last_changed_rev,
                     apr_pool_t *scratch_pool)
 {
   apr_hash_t *locations;
   const char *deleted_url;
   const char *deleted_location;
-  svn_ra_session_t *ra_session;
-  const char *corrected_url;
   apr_array_header_t *location_revisions;
-  svn_dirent_t *dirent;
-
-  *related = FALSE;
+  const char *old_session_url;
 
   location_revisions = apr_array_make(scratch_pool, 1, sizeof(svn_revnum_t));
   APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = copyfrom_rev;
@@ -304,13 +316,10 @@ check_move_ancestry(svn_boolean_t *relat
                                                  deleted_repos_relpath,
                                                  NULL),
                                      scratch_pool);
-  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
-                                               deleted_url, NULL,
-                                               NULL, FALSE, FALSE,
-                                               ctx, scratch_pool,
-                                               scratch_pool));
+  SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
+                                            deleted_url, scratch_pool));
   SVN_ERR(svn_ra_get_locations(ra_session, &locations, "",
-                               deleted_rev - 1, location_revisions,
+                               rev_below(deleted_rev), location_revisions,
                                scratch_pool));
 
   deleted_location = apr_hash_get(locations, &copyfrom_rev,
@@ -320,16 +329,33 @@ check_move_ancestry(svn_boolean_t *relat
       if (deleted_location[0] == '/')
         deleted_location++;
       if (strcmp(deleted_location, copyfrom_path) != 0)
-        return SVN_NO_ERROR;
+        {
+          *related = FALSE;
+          return SVN_NO_ERROR;
+        }
+    }
+  else
+    {
+      *related = FALSE;
+      return SVN_NO_ERROR;
     }
 
-  /* Verify that copyfrom_rev >= last-changed revision of the deleted node. */
-  SVN_ERR(svn_ra_stat(ra_session, "", deleted_rev - 1, &dirent, scratch_pool));
-  if (dirent == NULL || copyfrom_rev < dirent->created_rev)
-    return SVN_NO_ERROR;
+  if (check_last_changed_rev)
+    {
+      svn_dirent_t *dirent;
 
-  *related = TRUE;
+      /* Verify that copyfrom_rev >= last-changed revision of the
+       * deleted node. */
+      SVN_ERR(svn_ra_stat(ra_session, "", rev_below(deleted_rev), &dirent,
+                          scratch_pool));
+      if (dirent == NULL || copyfrom_rev < dirent->created_rev)
+        {
+          *related = FALSE;
+          return SVN_NO_ERROR;
+        }
+    }
 
+  *related = TRUE;
   return SVN_NO_ERROR;
 }
 
@@ -340,49 +366,48 @@ struct copy_info {
 };
 
 /* Update MOVES_TABLE and MOVED_PATHS based on information from
- * revision data in LOG_ENTRY, COPIES, and DELETED_PATHS. */
+ * revision data in LOG_ENTRY, COPIES, and DELETED_PATHS.
+ * Use RA_SESSION to perform the necessary requests. */
 static svn_error_t *
-find_moves_in_revision(apr_hash_t *moves_table,
+find_moves_in_revision(svn_ra_session_t *ra_session,
+                       apr_hash_t *moves_table,
                        apr_hash_t *moved_paths,
                        svn_log_entry_t *log_entry,
                        apr_hash_t *copies,
                        apr_array_header_t *deleted_paths,
                        const char *repos_root_url,
-                       svn_client_ctx_t *ctx,
                        apr_pool_t *result_pool,
                        apr_pool_t *scratch_pool)
 {
   apr_pool_t *iterpool;
-  apr_array_header_t *copies_with_same_source_path;
   svn_boolean_t related;
-  int i, j;
+  int i;
 
   iterpool = svn_pool_create(scratch_pool);
   for (i = 0; i < deleted_paths->nelts; i++)
     {
       const char *deleted_repos_relpath;
+      struct copy_info *copy;
+      struct repos_move_info *move;
+      struct repos_move_info *next_move;
+      svn_string_t *author;
+      apr_array_header_t *moves;
+      apr_array_header_t *copies_with_same_source_path;
+      int j;
+
+      svn_pool_clear(iterpool);
 
       deleted_repos_relpath = APR_ARRAY_IDX(deleted_paths, i, const char *);
 
-      /* See if we can match any copies to this deleted path. */
-      copies_with_same_source_path = apr_hash_get(copies,
-                                                  deleted_repos_relpath,
-                                                  APR_HASH_KEY_STRING);
+      copies_with_same_source_path = svn_hash_gets(copies,
+                                                   deleted_repos_relpath);
       if (copies_with_same_source_path == NULL)
-        continue;
+        continue; /* Not a move, or a nested move we handle later on. */
 
+      copy = NULL;
       for (j = 0; j < copies_with_same_source_path->nelts; j++)
         {
-          struct copy_info *copy;
-          struct repos_move_info *move;
-          struct repos_move_info *next_move;
-          svn_string_t *author;
-          apr_array_header_t *moves;
-          
-          svn_pool_clear(iterpool);
-
-          copy = APR_ARRAY_IDX(copies_with_same_source_path, j,
-                               struct copy_info *);
+          struct copy_info *this_copy;
 
           /* We found a deleted node which matches the copyfrom path of
            * a copied node. Verify that the deleted node is an ancestor
@@ -390,71 +415,79 @@ find_moves_in_revision(apr_hash_t *moves
            * from revision log_entry->revision-1 (where the deleted node is
            * guaranteed to exist) to the copyfrom-revision, we must end up
            * at the copyfrom-path. */
-          SVN_ERR(check_move_ancestry(&related, repos_root_url,
+          this_copy = APR_ARRAY_IDX(copies_with_same_source_path, j,
+                                    struct copy_info *);
+          SVN_ERR(check_move_ancestry(&related, ra_session, repos_root_url,
                                       deleted_repos_relpath,
                                       log_entry->revision,
-                                      copy->copyfrom_path,
-                                      copy->copyfrom_rev,
-                                      ctx, iterpool));
-          if (!related)
-            continue;
-
-          /* Remember details of this move. */
-          move = apr_pcalloc(result_pool, sizeof(*move));
-          move->moved_from_repos_relpath = apr_pstrdup(result_pool,
-                                                       deleted_repos_relpath);
-          move->moved_to_repos_relpath = apr_pstrdup(result_pool,
-                                                     copy->copyto_path);
-          move->rev = log_entry->revision;
-          author = svn_hash_gets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR);
-          move->rev_author = apr_pstrdup(result_pool, author->data);
-          move->copyfrom_rev = copy->copyfrom_rev;
-
-          /* Link together multiple moves of the same node.
-           * Note that we're traversing history backwards, so moves already
-           * present in the list happened in younger revisions. */
-          next_move = svn_hash_gets(moved_paths, move->moved_to_repos_relpath);
-          if (next_move)
-            {
-              /* Tracing back history of the delete-half of the next move
-               * to the copyfrom-revision of the prior move we must end up
-               * at the delete-half of the prior move. */
-              SVN_ERR(check_move_ancestry(&related, repos_root_url,
-                                          next_move->moved_from_repos_relpath,
-                                          next_move->rev,
-                                          move->moved_from_repos_relpath,
-                                          move->copyfrom_rev,
-                                          ctx, iterpool));
-              if (related)
-                {
-                  SVN_ERR_ASSERT(move->rev < next_move->rev);
-
-                  /* Prepend this move to the linked list. */
-                  if (move->next == NULL)
-                    move->next = apr_array_make(
-                                   result_pool, 1,
-                                   sizeof (struct repos_move_info *));
-                  APR_ARRAY_PUSH(move->next,
-                                 struct repos_move_info *) = next_move;
-                  next_move->prev = move;
-                }
+                                      this_copy->copyfrom_path,
+                                      this_copy->copyfrom_rev,
+                                      TRUE, iterpool));
+          if (related)
+            {
+              copy = this_copy;
+              break;
             }
+        }
 
-          /* Make this move the head of our next-move linking map. */
-          svn_hash_sets(moved_paths, move->moved_from_repos_relpath, move);
+      if (copy == NULL)
+        continue;
 
-          /* Add this move to the list of moves in this revision. */
-          moves = apr_hash_get(moves_table, &move->rev, sizeof(svn_revnum_t));
-          if (moves == NULL)
-            {
-              /* This is the first move in this revision. Create the list. */
-              moves = apr_array_make(result_pool, 1,
-                                     sizeof(struct repos_move_info *));
-              apr_hash_set(moves_table, &move->rev, sizeof(svn_revnum_t),
-                           moves);
+      /* Remember details of this move. */
+      move = apr_pcalloc(result_pool, sizeof(*move));
+      move->moved_from_repos_relpath = apr_pstrdup(result_pool,
+                                                   deleted_repos_relpath);
+      move->moved_to_repos_relpath = apr_pstrdup(result_pool,
+                                                 copy->copyto_path);
+      move->rev = log_entry->revision;
+      author = svn_hash_gets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR);
+      move->rev_author = apr_pstrdup(result_pool, author->data);
+      move->copyfrom_rev = copy->copyfrom_rev;
+
+      /* Link together multiple moves of the same node.
+       * Note that we're traversing history backwards, so moves already
+       * present in the list happened in younger revisions. */
+      next_move = svn_hash_gets(moved_paths, move->moved_to_repos_relpath);
+      if (next_move)
+        {
+          /* Tracing back history of the delete-half of the next move
+           * to the copyfrom-revision of the prior move we must end up
+           * at the delete-half of the prior move. */
+          SVN_ERR(check_move_ancestry(&related, ra_session, repos_root_url,
+                                      next_move->moved_from_repos_relpath,
+                                      next_move->rev,
+                                      move->moved_from_repos_relpath,
+                                      move->copyfrom_rev,
+                                      FALSE, iterpool));
+          if (related)
+            {
+              SVN_ERR_ASSERT(move->rev < next_move->rev);
+
+              /* Prepend this move to the linked list. */
+              if (move->next == NULL)
+                move->next = apr_array_make(
+                               result_pool, 1,
+                               sizeof (struct repos_move_info *));
+              APR_ARRAY_PUSH(move->next,
+                             struct repos_move_info *) = next_move;
+              next_move->prev = move;
             }
-          APR_ARRAY_PUSH(moves, struct repos_move_info *) = move;
         }
+
+      /* Make this move the head of our next-move linking map. */
+      svn_hash_sets(moved_paths, move->moved_from_repos_relpath, move);
+
+      /* Add this move to the list of moves in this revision. */
+      moves = apr_hash_get(moves_table, &move->rev, sizeof(svn_revnum_t));
+      if (moves == NULL)
+        {
+          /* This is the first move in this revision. Create the list. */
+          moves = apr_array_make(result_pool, 1,
+                                 sizeof(struct repos_move_info *));
+          apr_hash_set(moves_table, &move->rev, sizeof(svn_revnum_t),
+                       moves);
+        }
+      APR_ARRAY_PUSH(moves, struct repos_move_info *) = move;
     }
   svn_pool_destroy(iterpool);
 
@@ -527,6 +560,9 @@ struct find_deleted_rev_baton
   /* Temporary map of moved paths to struct repos_move_info.
    * Used to link multiple moves of the same node across revisions. */
   apr_hash_t *moved_paths;
+
+  /* Extra RA session that can be used to make additional requests. */
+  svn_ra_session_t *extra_ra_session;
 };
 
 /* Find the youngest common ancestor of REPOS_RELPATH1@PEG_REV1 and
@@ -562,6 +598,136 @@ find_yca(svn_client__pathrev_t **yca_loc
   return SVN_NO_ERROR;
 }
 
+/* If DELETED_RELPATH matches the moved-from path of a move in MOVES,
+ * or if DELETED_RELPATH is a child of a moved-to path in MOVES, return
+ * a struct move_info for the corresponding move. Else, return NULL. */
+static struct repos_move_info *
+map_deleted_path_to_move(const char *deleted_relpath,
+                         apr_array_header_t *moves,
+                         apr_pool_t *scratch_pool)
+{
+  int i;
+
+  for (i = 0; i < moves->nelts; i++)
+    {
+      struct repos_move_info *move;
+      const char *relpath;
+          
+      move = APR_ARRAY_IDX(moves, i, struct repos_move_info *);
+      if (strcmp(move->moved_from_repos_relpath, deleted_relpath) == 0)
+        return move;
+
+      relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
+                                          deleted_relpath);
+      /* ### Should probably return the closest path-wise ancestor. */
+      if (relpath)
+        return move;
+    }
+
+  return NULL;
+}
+
+static svn_error_t *
+find_nested_move(struct repos_move_info **nested_move,
+                 const char *deleted_repos_relpath,
+                 apr_array_header_t *moves,
+                 apr_hash_t *copies,
+                 apr_array_header_t *deleted_paths,
+                 svn_revnum_t revision,
+                 svn_string_t *author,
+                 const char *repos_root_url,
+                 const char *repos_uuid,
+                 svn_ra_session_t *ra_session,
+                 svn_client_ctx_t *ctx,
+                 apr_pool_t *result_pool,
+                 apr_pool_t *scratch_pool)
+{
+  int i;
+  apr_pool_t *iterpool;
+  struct copy_info *move_destination = NULL;
+  const char *moved_from_repos_relpath = NULL;
+
+  *nested_move = NULL;
+
+  iterpool = svn_pool_create(scratch_pool);
+  for (i = 0; i < deleted_paths->nelts; i++)
+    {
+      const char *deleted_path;
+      const char *child_relpath;
+      const char *moved_along_repos_relpath;
+      struct repos_move_info *move;
+      apr_array_header_t *copies_with_same_source_path;
+      struct copy_info *copy;
+      svn_boolean_t related;
+
+      svn_pool_clear(iterpool);
+
+      deleted_path = APR_ARRAY_IDX(deleted_paths, i, const char *);
+      move = map_deleted_path_to_move(deleted_path, moves, iterpool);
+      if (move == NULL)
+        continue;
+
+      child_relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
+                                                deleted_repos_relpath);
+      if (child_relpath == NULL || child_relpath[0] == '\0')
+        continue; /* not a nested move */
+
+      /* Consider: svn mv A B; svn mv B/foo C/foo
+       * Copyfrom for C/foo is A/foo, even though C/foo was moved here from
+       * B/foo. A/foo was not deleted. It is B/foo which was deleted.
+       * We now know about the move A->B and moved-along child_relpath "foo".
+       * Try to detect an ancestral relationship between A/foo and the
+       * moved-along path. */
+      moved_along_repos_relpath =
+        svn_relpath_join(move->moved_from_repos_relpath, child_relpath,
+                         iterpool);
+      copies_with_same_source_path = svn_hash_gets(copies,
+                                                   moved_along_repos_relpath);
+      if (copies_with_same_source_path == NULL)
+        continue;
+
+      if (copies_with_same_source_path->nelts > 1)
+        continue; /* ### handle ambiguity! */
+
+      copy = APR_ARRAY_IDX(copies_with_same_source_path, 0, struct copy_info *);
+      SVN_ERR(check_move_ancestry(&related, ra_session, repos_root_url,
+                                  moved_along_repos_relpath,
+                                  rev_below(revision),
+                                  copy->copyfrom_path,
+                                  copy->copyfrom_rev,
+                                  TRUE, iterpool));
+
+      if (related)
+        {
+          moved_from_repos_relpath =
+            apr_pstrdup(result_pool, moved_along_repos_relpath);
+          move_destination = copy;
+          break;
+        }
+    }
+  svn_pool_destroy(iterpool);
+
+  if (moved_from_repos_relpath && move_destination)
+    {
+      struct repos_move_info *move;
+
+      /* Remember details of this move. */
+      move = apr_pcalloc(result_pool, sizeof(*move));
+      move->moved_from_repos_relpath = moved_from_repos_relpath;
+      move->moved_to_repos_relpath =
+        apr_pstrdup(result_pool,
+                    move_destination->copyto_path);
+      move->rev = revision;
+      move->rev_author = apr_pstrdup(result_pool, author->data);
+      move->copyfrom_rev = move_destination->copyfrom_rev;
+
+      APR_ARRAY_PUSH(moves, struct repos_move_info *) = move;
+      *nested_move = move;
+    }
+
+  return SVN_NO_ERROR;
+}
+
 /* Implements svn_log_entry_receiver_t.
  *
  * Find the revision in which a node, optionally ancestrally related to the
@@ -580,7 +746,7 @@ find_yca(svn_client__pathrev_t **yca_loc
  * works in cases where we do not already know a revision in which the deleted
  * node once used to exist.
  * 
- * If the node node was moved, rather than deleted, return move information
+ * If the node was moved, rather than deleted, return move information
  * in BATON->MOVE.
  */
 static svn_error_t *
@@ -592,6 +758,7 @@ find_deleted_rev(void *baton,
   apr_hash_index_t *hi;
   apr_pool_t *iterpool;
   svn_boolean_t deleted_node_found = FALSE;
+  svn_node_kind_t replacing_node_kind = svn_node_none;
   apr_array_header_t *deleted_paths;
   apr_hash_t *copies;
 
@@ -655,20 +822,12 @@ find_deleted_rev(void *baton,
                          struct copy_info *) = copy;
         }
 
-      /* For move detection, store all deleted_paths.
-       *
-       * ### This also stores deletions which happened inside copies.
-       * ### But we are not able to handle them at present.
-       * ### Consider: cp A B; mv B/foo C/foo
-       * ### Copyfrom for C/foo is now A/foo, even though C/foo was moved
-       * ### here from B/foo. We don't detect such moves at present since
-       * ### A/foo was not deleted. It is B/foo which was deleted.
-       */
+      /* For move detection, store all deleted_paths. */
       if (log_item->action == 'D' || log_item->action == 'R')
         APR_ARRAY_PUSH(deleted_paths, const char *) =
           apr_pstrdup(scratch_pool, changed_path);
 
-      /* Check if we found the deleted node we're looking for. */
+      /* Check if we already found the deleted node we're looking for. */
       if (!deleted_node_found &&
           svn_path_compare_paths(b->deleted_repos_relpath, changed_path) == 0 &&
           (log_item->action == 'D' || log_item->action == 'R'))
@@ -689,7 +848,7 @@ find_deleted_rev(void *baton,
                              b->related_repos_relpath,
                              b->related_repos_peg_rev,
                              b->deleted_repos_relpath,
-                             log_entry->revision - 1,
+                             rev_below(log_entry->revision),
                              b->repos_root_url, b->repos_uuid,
                              b->ctx, iterpool, iterpool);
               if (err)
@@ -707,31 +866,57 @@ find_deleted_rev(void *baton,
               deleted_node_found = (yca_loc != NULL);
             }
 
-          if (deleted_node_found)
-            {
-              svn_string_t *author;
-
-              b->deleted_rev = log_entry->revision;
-              author = svn_hash_gets(log_entry->revprops,
-                                     SVN_PROP_REVISION_AUTHOR);
-              b->deleted_rev_author = apr_pstrdup(b->result_pool, author->data);
-                  
-              if (log_item->action == 'R')
-                b->replacing_node_kind = log_item->node_kind;
-              else
-                b->replacing_node_kind = svn_node_none;
-            }
+          if (deleted_node_found && log_item->action == 'R')
+            replacing_node_kind = log_item->node_kind;
         }
     }
   svn_pool_destroy(iterpool);
 
   /* Check for moves in this revision */
-  SVN_ERR(find_moves_in_revision(b->moves_table, b->moved_paths,
+  SVN_ERR(find_moves_in_revision(b->extra_ra_session,
+                                 b->moves_table, b->moved_paths,
                                  log_entry, copies, deleted_paths,
-                                 b->repos_root_url, b->ctx,
+                                 b->repos_root_url,
                                  b->result_pool, scratch_pool));
+  if (!deleted_node_found)
+    {
+      apr_array_header_t *moves;
+
+      moves = apr_hash_get(b->moves_table, &log_entry->revision,
+                           sizeof(svn_revnum_t));
+      if (moves)
+        {
+          struct repos_move_info *nested_move;
+
+          SVN_ERR(find_nested_move(&nested_move,
+                                   b->deleted_repos_relpath,
+                                   moves, copies, deleted_paths,
+                                   log_entry->revision,
+                                   svn_hash_gets(log_entry->revprops,
+                                                 SVN_PROP_REVISION_AUTHOR),
+                                   b->repos_root_url,
+                                   b->repos_uuid,
+                                   b->extra_ra_session, b->ctx,
+                                   b->result_pool, scratch_pool));
+          if (nested_move)
+            {
+              deleted_node_found = TRUE;
+              b->deleted_repos_relpath = nested_move->moved_from_repos_relpath;
+            }
+        }
+    }
+
   if (deleted_node_found)
     {
+      svn_string_t *author;
+
+      b->deleted_rev = log_entry->revision;
+      author = svn_hash_gets(log_entry->revprops,
+                             SVN_PROP_REVISION_AUTHOR);
+      b->deleted_rev_author = apr_pstrdup(b->result_pool, author->data);
+          
+      b->replacing_node_kind = replacing_node_kind;
+
       /* We're done. Abort the log operation. */
       return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL, NULL);
     }
@@ -802,12 +987,23 @@ describe_local_file_node_change(const ch
       case svn_wc_conflict_reason_moved_away:
         {
           const char *moved_to_abspath;
+          svn_error_t *err;
 
-          SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL, 
-                                              ctx->wc_ctx,
-                                              conflict->local_abspath,
-                                              scratch_pool,
-                                              scratch_pool));
+          err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL, 
+                                            ctx->wc_ctx,
+                                            conflict->local_abspath,
+                                            scratch_pool,
+                                            scratch_pool);
+          if (err)
+            {
+              if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+                {
+                  moved_to_abspath = NULL;
+                  svn_error_clear(err);
+                }
+              else
+                return svn_error_trace(err);
+            }
           if (operation == svn_wc_operation_update ||
               operation == svn_wc_operation_switch)
             {
@@ -1016,12 +1212,24 @@ describe_local_dir_node_change(const cha
       case svn_wc_conflict_reason_moved_away:
         {
           const char *moved_to_abspath;
+          svn_error_t *err;
+
+          err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL, 
+                                            ctx->wc_ctx,
+                                            conflict->local_abspath,
+                                            scratch_pool,
+                                            scratch_pool);
+          if (err)
+            {
+              if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+                {
+                  moved_to_abspath = NULL;
+                  svn_error_clear(err);
+                }
+              else
+                return svn_error_trace(err);
+            }
 
-          SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL, 
-                                              ctx->wc_ctx,
-                                              conflict->local_abspath,
-                                              scratch_pool,
-                                              scratch_pool));
           if (operation == svn_wc_operation_update ||
               operation == svn_wc_operation_switch)
             {
@@ -1235,6 +1443,8 @@ find_revision_for_suspected_deletion(svn
   b.moves_table = apr_hash_make(result_pool);
   b.moved_paths = apr_hash_make(scratch_pool);
   b.result_pool = result_pool;
+  SVN_ERR(svn_ra__dup_session(&b.extra_ra_session, ra_session, NULL,
+                              scratch_pool, scratch_pool));
 
   err = svn_ra_get_log2(ra_session, paths, start_rev, end_rev,
                         0, /* no limit */
@@ -1325,8 +1535,93 @@ struct conflict_tree_local_missing_detai
    * elements. Each element is the head of a move chain which starts in
    * DELETED_REV. */
   apr_array_header_t *moves;
+
+  /* If not NULL, this is the move target abspath. */
+  const char *moved_to_abspath;
 };
 
+static svn_error_t *
+find_related_node(const char **related_repos_relpath,
+                  svn_revnum_t *related_peg_rev,
+                  const char *younger_related_repos_relpath,
+                  svn_revnum_t younger_related_peg_rev,
+                  const char *older_repos_relpath,
+                  svn_revnum_t older_peg_rev,
+                  svn_client_conflict_t *conflict,
+                  svn_client_ctx_t *ctx,
+                  apr_pool_t *result_pool,
+                  apr_pool_t *scratch_pool)
+{
+  const char *repos_root_url;
+  const char *related_url;
+  const char *corrected_url;
+  svn_node_kind_t related_node_kind;
+  svn_ra_session_t *ra_session;
+
+  *related_repos_relpath = NULL;
+  *related_peg_rev = SVN_INVALID_REVNUM;
+
+  SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
+                                             conflict,
+                                             scratch_pool, scratch_pool));
+  related_url = svn_path_url_add_component2(repos_root_url,
+                                            younger_related_repos_relpath,
+                                            scratch_pool);
+  SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
+                                               &corrected_url,
+                                               related_url, NULL,
+                                               NULL,
+                                               FALSE,
+                                               FALSE,
+                                               ctx,
+                                               scratch_pool,
+                                               scratch_pool));
+  SVN_ERR(svn_ra_check_path(ra_session, "", younger_related_peg_rev,
+                            &related_node_kind, scratch_pool));
+  if (related_node_kind == svn_node_none)
+    {
+      svn_revnum_t related_deleted_rev;
+      const char *related_deleted_rev_author;
+      svn_node_kind_t related_replacing_node_kind;
+      const char *related_basename;
+      const char *related_parent_repos_relpath;
+      apr_array_header_t *related_moves;
+
+      /* Looks like the younger node, which we'd like to use as our
+       * 'related node', was deleted. Try to find its deleted revision
+       *  so we can calculate a peg revision at which it exists.
+       * The younger node is related to the older node, so we can use
+       * the older node to guide us in our search. */
+      related_basename = svn_relpath_basename(younger_related_repos_relpath,
+                                              scratch_pool);
+      related_parent_repos_relpath =
+        svn_relpath_dirname(younger_related_repos_relpath, scratch_pool);
+      SVN_ERR(find_revision_for_suspected_deletion(
+                &related_deleted_rev, &related_deleted_rev_author,
+                &related_replacing_node_kind, &related_moves,
+                conflict, related_basename,
+                related_parent_repos_relpath,
+                younger_related_peg_rev, 0,
+                older_repos_relpath, older_peg_rev,
+                ctx, conflict->pool, scratch_pool));
+
+      /* If we can't find a related node, bail. */
+      if (related_deleted_rev == SVN_INVALID_REVNUM)
+        return SVN_NO_ERROR;
+
+      /* The node should exist in the revision before it was deleted. */
+      *related_repos_relpath = younger_related_repos_relpath;
+      *related_peg_rev = rev_below(related_deleted_rev);
+    }
+  else
+    {
+      *related_repos_relpath = younger_related_repos_relpath;
+      *related_peg_rev = younger_related_peg_rev;
+    }
+
+  return SVN_NO_ERROR;
+}
+
 /* Implements tree_conflict_get_details_func_t. */
 static svn_error_t *
 conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
@@ -1347,10 +1642,6 @@ conflict_tree_get_details_local_missing(
   const char *related_repos_relpath;
   svn_revnum_t related_peg_rev;
 
-  /* We only handle merges here. */
-  if (svn_client_conflict_get_operation(conflict) != svn_wc_operation_merge)
-    return SVN_NO_ERROR;
-
   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
             &old_repos_relpath, &old_rev, NULL, conflict,
             scratch_pool, scratch_pool));
@@ -1358,9 +1649,8 @@ conflict_tree_get_details_local_missing(
             &new_repos_relpath, &new_rev, NULL, conflict,
             scratch_pool, scratch_pool));
 
-  /* A deletion of the node may have happened on the branch we
-   * merged to. Scan the conflict victim's parent's log to find
-   * a revision which deleted the node. */
+  /* Scan the conflict victim's parent's log to find a revision which
+   * deleted the node. */
   deleted_basename = svn_dirent_basename(conflict->local_abspath,
                                          scratch_pool);
   SVN_ERR(svn_wc__node_get_repos_info(NULL, &parent_repos_relpath,
@@ -1381,80 +1671,19 @@ conflict_tree_get_details_local_missing(
   /* Make sure we're going to search the related node in a revision where
    * it exists. The younger incoming node might have been deleted in HEAD. */
   if (related_repos_relpath != NULL && related_peg_rev != SVN_INVALID_REVNUM)
-    {
-      const char *repos_root_url;
-      const char *repos_uuid;
-      const char *related_url;
-      const char *corrected_url;
-      svn_node_kind_t related_node_kind;
-      svn_ra_session_t *ra_session;
-
-      SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url,
-                                                 &repos_uuid,
-                                                 conflict,
-                                                 scratch_pool, scratch_pool));
-      related_url = svn_path_url_add_component2(repos_root_url,
-                                                related_repos_relpath,
-                                                scratch_pool);
-      SVN_ERR(svn_client__open_ra_session_internal(&ra_session,
-                                                   &corrected_url,
-                                                   related_url, NULL,
-                                                   NULL,
-                                                   FALSE,
-                                                   FALSE,
-                                                   ctx,
-                                                   scratch_pool,
-                                                   scratch_pool));
-      SVN_ERR(svn_ra_check_path(ra_session, "", related_peg_rev,
-                                &related_node_kind, scratch_pool));
-      if (related_node_kind == svn_node_none)
-        {
-          svn_revnum_t related_deleted_rev;
-          const char *related_deleted_rev_author;
-          svn_node_kind_t related_replacing_node_kind;
-          const char *related_basename;
-          const char *related_parent_repos_relpath;
-          apr_array_header_t *related_moves;
-          const char *older_incoming_repos_relpath;
-          svn_revnum_t older_incoming_peg_rev;
-
-          /* Looks like the younger incoming node, which we'd like to use as
-           * our 'related node', was also deleted. Try to find its deleted
-           * revision so we can calculate a peg revision at which it exists.
-           * The younger incoming node is related to the older incoming node,
-           * so we can use the older incoming node to guide us in our search. */
-          related_basename = svn_relpath_basename(related_repos_relpath,
-                                                  scratch_pool);
-          related_parent_repos_relpath =
-            svn_relpath_dirname(related_repos_relpath, scratch_pool);
-          older_incoming_repos_relpath =
-                    (old_rev < new_rev ? old_repos_relpath : new_repos_relpath);
-          older_incoming_peg_rev = (old_rev < new_rev ? old_rev : new_rev);
-          SVN_ERR(find_revision_for_suspected_deletion(
-                    &related_deleted_rev, &related_deleted_rev_author,
-                    &related_replacing_node_kind, &related_moves,
-                    conflict, related_basename,
-                    related_parent_repos_relpath,
-                    old_rev < new_rev ? new_rev : old_rev, 0,
-                    older_incoming_repos_relpath, older_incoming_peg_rev,
-                    ctx, conflict->pool, scratch_pool));
-
-          /* If we can't find a related node, bail. */
-          if (related_deleted_rev == SVN_INVALID_REVNUM)
-            return SVN_NO_ERROR;
-
-          /* The node should exist in the revision before it was deleted. */
-          related_peg_rev = related_deleted_rev - 1;
-        }
-    }
+    SVN_ERR(find_related_node(
+              &related_repos_relpath, &related_peg_rev,
+              related_repos_relpath, related_peg_rev,
+              (old_rev < new_rev ? old_repos_relpath : new_repos_relpath),
+              (old_rev < new_rev ? old_rev : new_rev),
+              conflict, ctx, scratch_pool, scratch_pool));
     
   SVN_ERR(find_revision_for_suspected_deletion(
             &deleted_rev, &deleted_rev_author, &replacing_node_kind, &moves,
             conflict, deleted_basename, parent_repos_relpath,
             old_rev < new_rev ? new_rev : old_rev, 0,
-            related_repos_relpath,
-            related_peg_rev, ctx,
-            conflict->pool, scratch_pool));
+            related_repos_relpath, related_peg_rev,
+            ctx, conflict->pool, scratch_pool));
 
   if (deleted_rev == SVN_INVALID_REVNUM)
     return SVN_NO_ERROR;
@@ -1892,11 +2121,9 @@ conflict_tree_get_incoming_description_g
   svn_node_kind_t incoming_kind;
   svn_wc_conflict_action_t conflict_action;
   svn_wc_operation_t conflict_operation;
-  svn_node_kind_t conflict_node_kind;
 
   conflict_action = svn_client_conflict_get_incoming_change(conflict);
   conflict_operation = svn_client_conflict_get_operation(conflict);
-  conflict_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
 
   /* Determine the node kind of the incoming change. */
   incoming_kind = svn_node_unknown;
@@ -3282,7 +3509,8 @@ get_incoming_delete_details_for_reverse_
     {
       svn_node_kind_t replaced_node_kind;
 
-      SVN_ERR(svn_ra_check_path(ra_session, "", (*details)->added_rev - 1,
+      SVN_ERR(svn_ra_check_path(ra_session, "",
+                                rev_below((*details)->added_rev),
                                 &replaced_node_kind, scratch_pool));
       if (replaced_node_kind != svn_node_none)
         SVN_ERR(svn_ra_check_path(ra_session, "", (*details)->added_rev,
@@ -3303,19 +3531,20 @@ conflict_tree_get_details_incoming_delet
   const char *old_repos_relpath;
   const char *new_repos_relpath;
   const char *repos_root_url;
-  const char *repos_uuid;
   svn_revnum_t old_rev;
   svn_revnum_t new_rev;
+  svn_node_kind_t old_kind;
+  svn_node_kind_t new_kind;
   struct conflict_tree_incoming_delete_details *details;
   svn_wc_operation_t operation;
 
   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
-            &old_repos_relpath, &old_rev, NULL, conflict, scratch_pool,
+            &old_repos_relpath, &old_rev, &old_kind, conflict, scratch_pool,
             scratch_pool));
   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
-            &new_repos_relpath, &new_rev, NULL, conflict, scratch_pool,
+            &new_repos_relpath, &new_rev, &new_kind, conflict, scratch_pool,
             scratch_pool));
-  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));
   operation = svn_client_conflict_get_operation(conflict);
@@ -3329,9 +3558,10 @@ conflict_tree_get_details_incoming_delet
           const char *deleted_rev_author;
           svn_node_kind_t replacing_node_kind;
           apr_array_header_t *moves;
+          const char *related_repos_relpath;
+          svn_revnum_t related_peg_rev;
 
           /* The update operation went forward in history. */
-
           SVN_ERR(svn_wc__node_get_repos_info(NULL, &parent_repos_relpath,
                                               NULL, NULL,
                                               ctx->wc_ctx,
@@ -3340,12 +3570,29 @@ conflict_tree_get_details_incoming_delet
                                                 scratch_pool),
                                               scratch_pool,
                                               scratch_pool));
+          if (new_kind == svn_node_none)
+            {
+              SVN_ERR(find_related_node(&related_repos_relpath,
+                                        &related_peg_rev,
+                                        new_repos_relpath, new_rev,
+                                        old_repos_relpath, old_rev,
+                                        conflict, ctx,
+                                        scratch_pool, scratch_pool));
+            }
+          else
+            {
+              /* related to self */
+              related_repos_relpath = NULL;
+              related_peg_rev = SVN_INVALID_REVNUM;
+            }
+
           SVN_ERR(find_revision_for_suspected_deletion(
                     &deleted_rev, &deleted_rev_author, &replacing_node_kind,
                     &moves, conflict,
                     svn_dirent_basename(conflict->local_abspath, scratch_pool),
-                    parent_repos_relpath, new_rev, old_rev,
-                    NULL, SVN_INVALID_REVNUM, /* related to self */
+                    parent_repos_relpath,
+                    new_rev, new_kind == svn_node_none ? 0 : old_rev,
+                    related_repos_relpath, related_peg_rev,
                     ctx, conflict->pool, scratch_pool));
           if (deleted_rev == SVN_INVALID_REVNUM)
             {
@@ -3396,7 +3643,7 @@ conflict_tree_get_details_incoming_delet
                     svn_relpath_basename(new_repos_relpath, scratch_pool),
                     svn_relpath_dirname(new_repos_relpath, scratch_pool),
                     new_rev, old_rev, old_repos_relpath, old_rev, ctx,
-                    scratch_pool, scratch_pool));
+                    conflict->pool, scratch_pool));
           if (deleted_rev == SVN_INVALID_REVNUM)
             {
               /* We could not determine the revision in which the node was
@@ -3472,7 +3719,6 @@ conflict_tree_get_details_incoming_add(s
   const char *old_repos_relpath;
   const char *new_repos_relpath;
   const char *repos_root_url;
-  const char *repos_uuid;
   svn_revnum_t old_rev;
   svn_revnum_t new_rev;
   struct conflict_tree_incoming_add_details *details;
@@ -3484,7 +3730,7 @@ conflict_tree_get_details_incoming_add(s
   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
             &new_repos_relpath, &new_rev, NULL, conflict, scratch_pool,
             scratch_pool));
-  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));
   operation = svn_client_conflict_get_operation(conflict);
@@ -3902,7 +4148,7 @@ describe_incoming_reverse_deletion_upon_
                             _("A new directory appeared during reverse-merge "
                               "of\n'^/%s:%ld-%ld'.\n"
                               "It was deleted by %s in r%ld."),
-                            old_repos_relpath, new_rev, old_rev - 1,
+                            old_repos_relpath, new_rev, rev_below(old_rev),
                             details->deleted_rev_author,
                             details->deleted_rev);
     }
@@ -4148,7 +4394,6 @@ conflict_tree_get_details_incoming_edit(
   const char *old_repos_relpath;
   const char *new_repos_relpath;
   const char *repos_root_url;
-  const char *repos_uuid;
   svn_revnum_t old_rev;
   svn_revnum_t new_rev;
   svn_node_kind_t old_node_kind;
@@ -4167,26 +4412,18 @@ conflict_tree_get_details_incoming_edit(
   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
             &new_repos_relpath, &new_rev, &new_node_kind, conflict,
             scratch_pool, scratch_pool));
-  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));
   operation = svn_client_conflict_get_operation(conflict);
-
-  b.ctx = ctx;
-  b.victim_abspath = svn_client_conflict_get_local_abspath(conflict);
-  b.result_pool = conflict->pool;
-  b.scratch_pool = scratch_pool;
-  b.edits = apr_array_make(
-               conflict->pool, 0,
-               sizeof(struct conflict_tree_incoming_edit_details *));
-  paths = apr_array_make(scratch_pool, 1, sizeof(const char *));
-  APR_ARRAY_PUSH(paths, const char *) = "";
-
-  revprops = apr_array_make(scratch_pool, 1, sizeof(const char *));
-  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
-
   if (operation == svn_wc_operation_update)
     {
+      b.node_kind = old_rev < new_rev ? new_node_kind : old_node_kind;
+
+      /* If there is no node then we cannot find any edits. */
+      if (b.node_kind == svn_node_none)
+        return SVN_NO_ERROR;
+
       url = svn_path_url_add_component2(repos_root_url,
                                         old_rev < new_rev ? new_repos_relpath
                                                           : old_repos_relpath,
@@ -4194,7 +4431,6 @@ conflict_tree_get_details_incoming_edit(
 
       b.repos_relpath = old_rev < new_rev ? new_repos_relpath
                                           : old_repos_relpath;
-      b.node_kind = old_rev < new_rev ? new_node_kind : old_node_kind;
     }
   else if (operation == svn_wc_operation_switch ||
            operation == svn_wc_operation_merge)
@@ -4215,6 +4451,20 @@ conflict_tree_get_details_incoming_edit(
                                                scratch_pool,
                                                scratch_pool));
 
+  paths = apr_array_make(scratch_pool, 1, sizeof(const char *));
+  APR_ARRAY_PUSH(paths, const char *) = "";
+
+  revprops = apr_array_make(scratch_pool, 1, sizeof(const char *));
+  APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
+
+  b.ctx = ctx;
+  b.victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+  b.result_pool = conflict->pool;
+  b.scratch_pool = scratch_pool;
+  b.edits = apr_array_make(
+               conflict->pool, 0,
+               sizeof(struct conflict_tree_incoming_edit_details *));
+
   SVN_ERR(svn_ra_get_log2(ra_session, paths,
                           old_rev < new_rev ? old_rev : new_rev,
                           old_rev < new_rev ? new_rev : old_rev,
@@ -4382,7 +4632,6 @@ conflict_tree_get_description_incoming_e
   apr_pool_t *scratch_pool)
 {
   const char *action;
-  svn_node_kind_t victim_node_kind;
   svn_wc_operation_t conflict_operation;
   const char *old_repos_relpath;
   svn_revnum_t old_rev;
@@ -4405,7 +4654,6 @@ conflict_tree_get_description_incoming_e
             scratch_pool, scratch_pool));
 
   conflict_operation = svn_client_conflict_get_operation(conflict);
-  victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
 
   edits = conflict->tree_conflict_incoming_details;
 
@@ -4890,16 +5138,12 @@ verify_local_state_for_incoming_add_upon
       if (option_id == svn_client_conflict_option_incoming_add_ignore)
         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
                                  _("Cannot resolve tree conflict on '%s' "
-                                   "by ignoring the incoming addition "
                                    "(expected a base node but found none)"),
                                  local_style_relpath);
       else if (option_id ==
-               svn_client_conflict_option_incoming_added_file_replace ||
-               option_id ==
                svn_client_conflict_option_incoming_added_dir_replace)
         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, err,
                                  _("Cannot resolve tree conflict on '%s' "
-                                   "by replacing the locally added node "
                                    "(expected a base node but found none)"),
                                  local_style_relpath);
       else
@@ -4913,20 +5157,16 @@ verify_local_state_for_incoming_add_upon
     {
       if (option_id == svn_client_conflict_option_incoming_add_ignore)
         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                                 _("Cannot resolve tree conflict on '%s' by "
-                                   "ignoring the incoming addition "
+                                 _("Cannot resolve tree conflict on '%s' "
                                    "(expected base node kind '%s', "
                                    "but found '%s')"),
                                  local_style_relpath,
                                  svn_node_kind_to_word(incoming_new_kind),
                                  svn_node_kind_to_word(base_kind));
       else if (option_id ==
-               svn_client_conflict_option_incoming_added_file_replace ||
-               option_id ==
                svn_client_conflict_option_incoming_added_dir_replace)
         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
                                  _("Cannot resolve tree conflict on '%s' "
-                                   "by replacing the locally added node "
                                    "(expected base node kind '%s', "
                                    "but found '%s')"),
                                   local_style_relpath,
@@ -4942,8 +5182,7 @@ verify_local_state_for_incoming_add_upon
     {
       if (option_id == svn_client_conflict_option_incoming_add_ignore)
         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                                 _("Cannot resolve tree conflict on '%s' by "
-                                   "ignoring the incoming addition "
+                                 _("Cannot resolve tree conflict on '%s' "
                                    "(expected base node from '^/%s@%ld', "
                                    "but found '^/%s@%ld')"),
                                  local_style_relpath,
@@ -4951,12 +5190,9 @@ verify_local_state_for_incoming_add_upon
                                  incoming_new_pegrev,
                                  base_repos_relpath, base_rev);
       else if (option_id ==
-               svn_client_conflict_option_incoming_added_file_replace ||
-               option_id ==
                svn_client_conflict_option_incoming_added_dir_replace)
         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
                                  _("Cannot resolve tree conflict on '%s' "
-                                   "by replacing the locally added node "
                                    "(expected base node from '^/%s@%ld', "
                                    "but found '^/%s@%ld')"),
                                  local_style_relpath,
@@ -4974,19 +5210,15 @@ verify_local_state_for_incoming_add_upon
     {
       if (option_id == svn_client_conflict_option_incoming_add_ignore)
         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
-                                 _("Cannot resolve tree conflict on '%s' by "
-                                   "ignoring the incoming addition "
+                                 _("Cannot resolve tree conflict on '%s' "
                                    "(expected an added item, but the item "
                                    "is not added)"),
                                  local_style_relpath);
 
       else if (option_id ==
-               svn_client_conflict_option_incoming_added_file_replace ||
-               option_id ==
                svn_client_conflict_option_incoming_added_dir_replace)
         return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
                                  _("Cannot resolve tree conflict on '%s' "
-                                   "by replacing the locally added node "
                                    "(expected an added item, but the item "
                                    "is not added)"),
                                  local_style_relpath);
@@ -5050,19 +5282,222 @@ unlock_wc:
 
   return SVN_NO_ERROR;
 }
-/* Implements conflict_option_resolve_func_t. */
-static svn_error_t *
-resolve_merge_incoming_added_file_text_merge(
-  svn_client_conflict_option_t *option,
-  svn_client_conflict_t *conflict,
-  svn_client_ctx_t *ctx,
-  apr_pool_t *scratch_pool)
+
+/* Delete entry and wc props from a set of properties. */
+static void
+filter_props(apr_hash_t *props, apr_pool_t *scratch_pool)
 {
-  svn_ra_session_t *ra_session;
-  const char *url;
+  apr_hash_index_t *hi;
+
+  for (hi = apr_hash_first(scratch_pool, props);
+       hi != NULL;
+       hi = apr_hash_next(hi))
+    {
+      const char *propname = apr_hash_this_key(hi);
+
+      if (!svn_wc_is_normal_prop(propname))
+        svn_hash_sets(props, propname, NULL);
+    }
+}
+
+/* Get KEYWORDS for LOCAL_ABSPATH.
+ * WC_CTX is a context for the working copy the patch is applied to.
+ * Use RESULT_POOL for allocations of fields in TARGET.
+ * Use SCRATCH_POOL for all other allocations. */
+static svn_error_t *
+get_keywords(apr_hash_t **keywords,
+             svn_wc_context_t *wc_ctx,
+             const char *local_abspath,
+             apr_pool_t *result_pool,
+             apr_pool_t *scratch_pool)
+{
+  apr_hash_t *props;
+  svn_string_t *keywords_val;
+
+  *keywords = NULL;
+  SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath,
+                            scratch_pool, scratch_pool));
+  keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS);
+  if (keywords_val)
+    {
+      svn_revnum_t changed_rev;
+      apr_time_t changed_date;
+      const char *rev_str;
+      const char *author;
+      const char *url;
+      const char *repos_root_url;
+      const char *repos_relpath;
+
+      SVN_ERR(svn_wc__node_get_changed_info(&changed_rev,
+                                            &changed_date,
+                                            &author, wc_ctx,
+                                            local_abspath,
+                                            scratch_pool,
+                                            scratch_pool));
+      rev_str = apr_psprintf(scratch_pool, "%ld", changed_rev);
+      SVN_ERR(svn_wc__node_get_repos_info(NULL, &repos_relpath, &repos_root_url,
+                                          NULL,
+                                          wc_ctx, local_abspath,
+                                          scratch_pool, scratch_pool));
+      url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+                                        scratch_pool);
+
+      SVN_ERR(svn_subst_build_keywords3(keywords,
+                                        keywords_val->data,
+                                        rev_str, url, repos_root_url,
+                                        changed_date,
+                                        author, result_pool));
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Implements conflict_option_resolve_func_t. */
+static svn_error_t *
+resolve_merge_incoming_added_file_text_update(
+  svn_client_conflict_option_t *option,
+  svn_client_conflict_t *conflict,
+  svn_client_ctx_t *ctx,
+  apr_pool_t *scratch_pool)
+{
+  const char *wc_tmpdir;
+  const char *local_abspath;
+  const char *lock_abspath;
+  svn_wc_merge_outcome_t merge_content_outcome;
+  svn_wc_notify_state_t merge_props_outcome;
+  const char *empty_file_abspath;
+  const char *working_file_tmp_abspath;
+  svn_stream_t *working_file_stream;
+  svn_stream_t *working_file_tmp_stream;
+  svn_stream_t *normalized_stream;
+  apr_hash_t *working_props;
+  apr_array_header_t *propdiffs;
+  svn_error_t *err;
+  apr_hash_t *keywords;
+
+  local_abspath = svn_client_conflict_get_local_abspath(conflict);
+
+  /* Set up tempory storage for the working version of file. */
+  SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
+                             scratch_pool, scratch_pool));
+  SVN_ERR(svn_stream_open_unique(&working_file_tmp_stream,
+                                 &working_file_tmp_abspath, wc_tmpdir,
+                                 /* Don't delete automatically! */
+                                 svn_io_file_del_none,
+                                 scratch_pool, scratch_pool));
+
+  /* Copy the working file to temporary storage. */
+  SVN_ERR(svn_stream_open_readonly(&working_file_stream, local_abspath,
+                                   scratch_pool, scratch_pool));
+  SVN_ERR(get_keywords(&keywords, ctx->wc_ctx, local_abspath,
+                       scratch_pool, scratch_pool));
+  normalized_stream = svn_subst_stream_translated(working_file_stream,
+                                                  "\n", TRUE,
+                                                  keywords, FALSE,
+                                                  scratch_pool);
+  SVN_ERR(svn_stream_copy3(normalized_stream, working_file_tmp_stream,
+                           ctx->cancel_func, ctx->cancel_baton,
+                           scratch_pool));
+
+  /* Get a copy of the working file's properties. */
+  SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath,
+                            scratch_pool, scratch_pool));
+  filter_props(working_props, scratch_pool);
+
+  /* Create an empty file as fake "merge-base" for the two added files.
+   * The files are not ancestrally related so this is the best we can do. */
+  SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file_abspath, NULL,
+                                   svn_io_file_del_on_pool_cleanup,
+                                   scratch_pool, scratch_pool));
+
+  /* Create a property diff which shows all props as added. */
+  SVN_ERR(svn_prop_diffs(&propdiffs, working_props,
+                         apr_hash_make(scratch_pool), scratch_pool));
+
+  /* ### The following WC modifications should be atomic. */
+  SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
+                                                 local_abspath,
+                                                 scratch_pool, scratch_pool));
+
+  /* Revert the path in order to restore the repository's line of
+   * history, which is part of the BASE tree. This revert operation
+   * is why are being careful about not losing the temporary copy. */
+  err = svn_wc_revert5(ctx->wc_ctx, local_abspath, svn_depth_empty,
+                       FALSE, NULL, TRUE, FALSE,
+                       NULL, NULL, /* no cancellation */
+                       ctx->notify_func2, ctx->notify_baton2,
+                       scratch_pool);
+  if (err)
+    goto unlock_wc;
+
+  /* Perform the file merge. ### Merge into tempfile and then rename on top? */
+  err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
+                      ctx->wc_ctx, empty_file_abspath,
+                      working_file_tmp_abspath, local_abspath,
+                      NULL, NULL, NULL, /* labels */
+                      NULL, NULL, /* conflict versions */
+                      FALSE, /* dry run */
+                      NULL, NULL, /* diff3_cmd, merge_options */
+                      NULL, propdiffs,
+                      NULL, NULL, /* conflict func/baton */
+                      NULL, NULL, /* don't allow user to cancel here */
+                      scratch_pool);
+
+unlock_wc:
+  if (err)
+      err = svn_error_quick_wrapf(
+              err, _("If needed, a backup copy of '%s' can be found at '%s'"),
+              svn_dirent_local_style(local_abspath, scratch_pool),
+              svn_dirent_local_style(working_file_tmp_abspath, scratch_pool));
+  err = svn_error_compose_create(err,
+                                 svn_wc__release_write_lock(ctx->wc_ctx,
+                                                            lock_abspath,
+                                                            scratch_pool));
+  svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
+  SVN_ERR(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(local_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(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);
+
+  /* All is good -- remove temporary copy of the working file. */
+  SVN_ERR(svn_io_remove_file2(working_file_tmp_abspath, TRUE, scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+/* Implements conflict_option_resolve_func_t. */
+static svn_error_t *
+resolve_merge_incoming_added_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_ra_session_t *ra_session;
+  const char *url;
   const char *corrected_url;
   const char *repos_root_url;
-  const char *repos_uuid;
   const char *wc_tmpdir;
   const char *incoming_new_repos_relpath;
   svn_revnum_t incoming_new_pegrev;
@@ -5075,7 +5510,6 @@ resolve_merge_incoming_added_file_text_m
   const char *empty_file_abspath;
   svn_stream_t *incoming_new_stream;
   apr_hash_t *incoming_new_props;
-  apr_hash_index_t *hi;
   apr_array_header_t *propdiffs;
   svn_error_t *err;
 
@@ -5096,7 +5530,7 @@ resolve_merge_incoming_added_file_text_m
             &incoming_new_repos_relpath, &incoming_new_pegrev,
             NULL, conflict, scratch_pool,
             scratch_pool));
-  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));
   url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath,
@@ -5113,16 +5547,7 @@ resolve_merge_incoming_added_file_text_m
   SVN_ERR(svn_stream_close(incoming_new_stream));
   SVN_ERR(svn_io_file_flush(incoming_new_file, scratch_pool));
 
-  /* Delete entry and wc props from the returned set of properties.. */
-  for (hi = apr_hash_first(scratch_pool, incoming_new_props);
-       hi != NULL;
-       hi = apr_hash_next(hi))
-    {
-      const char *propname = apr_hash_this_key(hi);
-
-      if (!svn_wc_is_normal_prop(propname))
-        svn_hash_sets(incoming_new_props, propname, NULL);
-    }
+  filter_props(incoming_new_props, scratch_pool);
 
   /* Create an empty file as fake "merge-base" for the two added files.
    * The files are not ancestrally related so this is the best we can do. */
@@ -5190,21 +5615,18 @@ resolve_merge_incoming_added_file_text_m
   return SVN_NO_ERROR;
 }
 
-/* Resolve a file/file "incoming add vs local obstruction" tree conflict by
- * replacing the local file with the incoming file. If MERGE_FILES is set,
- * also merge the files after replacing. */
-static svn_error_t *
-merge_incoming_added_file_replace(svn_client_conflict_option_t *option,
-                                  svn_client_conflict_t *conflict,
-                                  svn_client_ctx_t *ctx,
-                                  svn_boolean_t merge_files,
-                                  apr_pool_t *scratch_pool)
+/* Implements conflict_option_resolve_func_t. */
+static svn_error_t *
+resolve_merge_incoming_added_file_replace_and_merge(
+  svn_client_conflict_option_t *option,
+  svn_client_conflict_t *conflict,
+  svn_client_ctx_t *ctx,
+  apr_pool_t *scratch_pool)
 {
   svn_ra_session_t *ra_session;
   const char *url;
   const char *corrected_url;
   const char *repos_root_url;
-  const char *repos_uuid;
   const char *incoming_new_repos_relpath;
   svn_revnum_t incoming_new_pegrev;
   apr_file_t *incoming_new_file;
@@ -5215,9 +5637,16 @@ merge_incoming_added_file_replace(svn_cl
   const char *wc_tmpdir;
   svn_stream_t *working_file_tmp_stream;
   const char *working_file_tmp_abspath;
+  svn_stream_t *normalized_stream;
   svn_stream_t *working_file_stream;
   apr_hash_t *working_props;
+  apr_hash_t *keywords;
   svn_error_t *err;
+  svn_wc_merge_outcome_t merge_content_outcome;
+  svn_wc_notify_state_t merge_props_outcome;
+  apr_file_t *empty_file;
+  const char *empty_file_abspath;
+  apr_array_header_t *propdiffs;
 
   local_abspath = svn_client_conflict_get_local_abspath(conflict);
 
@@ -5232,7 +5661,13 @@ merge_incoming_added_file_replace(svn_cl
   /* Copy the working file to temporary storage. */
   SVN_ERR(svn_stream_open_readonly(&working_file_stream, local_abspath,
                                    scratch_pool, scratch_pool));
-  SVN_ERR(svn_stream_copy3(working_file_stream, working_file_tmp_stream,
+  SVN_ERR(get_keywords(&keywords, ctx->wc_ctx, local_abspath,
+                       scratch_pool, scratch_pool));
+  normalized_stream = svn_subst_stream_translated(working_file_stream,
+                                                  "\n", TRUE,
+                                                  keywords, FALSE,
+                                                  scratch_pool);
+  SVN_ERR(svn_stream_copy3(normalized_stream, working_file_tmp_stream,
                            ctx->cancel_func, ctx->cancel_baton,
                            scratch_pool));
 
@@ -5245,7 +5680,7 @@ merge_incoming_added_file_replace(svn_cl
             &incoming_new_repos_relpath, &incoming_new_pegrev,
             NULL, conflict, scratch_pool,
             scratch_pool));
-  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));
   url = svn_path_url_add_component2(repos_root_url, incoming_new_repos_relpath,
@@ -5309,70 +5744,51 @@ merge_incoming_added_file_replace(svn_cl
   if (err)
     goto unlock_wc;
 
-  if (merge_files)
-    {
-      svn_wc_merge_outcome_t merge_content_outcome;
-      svn_wc_notify_state_t merge_props_outcome;
-      apr_file_t *empty_file;
-      const char *empty_file_abspath;
-      apr_array_header_t *propdiffs;
-      apr_hash_index_t *hi;
-
-      /* Create an empty file as fake "merge-base" for the two added files.
-       * The files are not ancestrally related so this is the best we can do. */
-      err = svn_io_open_unique_file3(&empty_file, &empty_file_abspath, NULL,
-                                     svn_io_file_del_on_pool_cleanup,
-                                     scratch_pool, scratch_pool);
-      if (err)
-        goto unlock_wc;
-
-      /* Delete entry and wc props from the returned set of properties.. */
-      for (hi = apr_hash_first(scratch_pool, incoming_new_props);
-           hi != NULL;
-           hi = apr_hash_next(hi))
-        {
-          const char *propname = apr_hash_this_key(hi);
+  /* Create an empty file as fake "merge-base" for the two added files.
+   * The files are not ancestrally related so this is the best we can do. */
+  err = svn_io_open_unique_file3(&empty_file, &empty_file_abspath, NULL,
+                                 svn_io_file_del_on_pool_cleanup,
+                                 scratch_pool, scratch_pool);
+  if (err)
+    goto unlock_wc;
 
-          if (!svn_wc_is_normal_prop(propname))
-            svn_hash_sets(incoming_new_props, propname, NULL);
-        }
+  filter_props(incoming_new_props, scratch_pool);
 
-      /* Create a property diff for the files. */
-      err = svn_prop_diffs(&propdiffs, incoming_new_props,
-                           working_props, scratch_pool);
-      if (err)
-        goto unlock_wc;
+  /* Create a property diff for the files. */
+  err = svn_prop_diffs(&propdiffs, incoming_new_props,
+                       working_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, empty_file_abspath,
-                          working_file_tmp_abspath, local_abspath,
-                          NULL, NULL, NULL, /* labels */
-                          NULL, NULL, /* conflict versions */
-                          FALSE, /* dry run */
-                          NULL, NULL, /* diff3_cmd, merge_options */
-                          NULL, propdiffs,
-                          NULL, NULL, /* conflict func/baton */
-                          NULL, NULL, /* don't allow user to cancel here */
-                          scratch_pool);
-      if (err)
-        goto unlock_wc;
+  /* Perform the file merge. */
+  err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
+                      ctx->wc_ctx, empty_file_abspath,
+                      working_file_tmp_abspath, local_abspath,
+                      NULL, NULL, NULL, /* labels */
+                      NULL, NULL, /* conflict versions */
+                      FALSE, /* dry run */
+                      NULL, NULL, /* diff3_cmd, merge_options */
+                      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 = svn_wc_create_notify(
-                                       local_abspath,
-                                       svn_wc_notify_update_update,
-                                       scratch_pool);
+  if (ctx->notify_func2)
+    {
+      svn_wc_notify_t *notify = svn_wc_create_notify(
+                                   local_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);
-        }
+      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);
     }
 
 unlock_wc:
@@ -5399,105 +5815,475 @@ unlock_wc:
   return SVN_NO_ERROR;
 }
 
-/* Implements conflict_option_resolve_func_t. */
 static svn_error_t *
-resolve_merge_incoming_added_file_replace(
-  svn_client_conflict_option_t *option,
-  svn_client_conflict_t *conflict,
-  svn_client_ctx_t *ctx,
-  apr_pool_t *scratch_pool)
+raise_tree_conflict(const char *local_abspath,
+                    svn_wc_conflict_action_t incoming_change,
+                    svn_wc_conflict_reason_t local_change,
+                    svn_node_kind_t local_node_kind,
+                    svn_node_kind_t merge_left_kind,
+                    svn_node_kind_t merge_right_kind,
+                    const char *repos_root_url,
+                    const char *repos_uuid,
+                    const char *repos_relpath,
+                    svn_revnum_t merge_left_rev,
+                    svn_revnum_t merge_right_rev,
+                    svn_wc_context_t *wc_ctx,
+                    svn_wc_notify_func2_t notify_func2,
+                    void *notify_baton2,
+                    apr_pool_t *scratch_pool)
 {
-  return svn_error_trace(merge_incoming_added_file_replace(option,
-                                                           conflict,
-                                                           ctx,
-                                                           FALSE,
-                                                           scratch_pool));
+  svn_wc_conflict_description2_t *conflict;
+  const svn_wc_conflict_version_t *left_version;
+  const svn_wc_conflict_version_t *right_version;
+
+  left_version = svn_wc_conflict_version_create2(repos_root_url,
+                                                 repos_uuid,
+                                                 repos_relpath,
+                                                 merge_left_rev,
+                                                 merge_left_kind,
+                                                 scratch_pool);
+  right_version = svn_wc_conflict_version_create2(repos_root_url,
+                                                  repos_uuid,
+                                                  repos_relpath,
+                                                  merge_right_rev,
+                                                  merge_right_kind,
+                                                  scratch_pool);
+  conflict = svn_wc_conflict_description_create_tree2(local_abspath,
+                                                      local_node_kind,
+                                                      svn_wc_operation_merge,
+                                                      left_version,
+                                                      right_version,
+                                                      scratch_pool);
+  conflict->action = incoming_change;
+  conflict->reason = local_change;
+
+  SVN_ERR(svn_wc__add_tree_conflict(wc_ctx, conflict, scratch_pool));
+
+  if (notify_func2)
+    {
+      svn_wc_notify_t *notify;
+
+      notify = svn_wc_create_notify(local_abspath, svn_wc_notify_tree_conflict,
+                                    scratch_pool);
+      notify->kind = local_node_kind;
+      notify_func2(notify_baton2, notify, scratch_pool);
+    }
+
+  return SVN_NO_ERROR;
 }
 
-/* Implements conflict_option_resolve_func_t. */
+struct merge_newly_added_dir_baton {
+  const char *target_abspath;
+  svn_client_ctx_t *ctx;
+  const char *repos_root_url;
+  const char *repos_uuid;
+  const char *added_repos_relpath;
+  svn_revnum_t merge_left_rev;
+  svn_revnum_t merge_right_rev;
+};
+
 static svn_error_t *
-resolve_merge_incoming_added_file_replace_and_merge(
-  svn_client_conflict_option_t *option,
-  svn_client_conflict_t *conflict,
-  svn_client_ctx_t *ctx,
-  apr_pool_t *scratch_pool)
+merge_added_dir_props(const char *target_abspath,
+                      const char *added_repos_relpath,
+                      apr_hash_t *added_props,
+                      const char *repos_root_url,
+                      const char *repos_uuid,
+                      svn_revnum_t merge_left_rev,
+                      svn_revnum_t merge_right_rev,
+                      svn_client_ctx_t *ctx,
+                      apr_pool_t *scratch_pool)
 {
-  return svn_error_trace(merge_incoming_added_file_replace(option,
-                                                           conflict,
-                                                           ctx,
-                                                           TRUE,
-                                                           scratch_pool));
+  svn_wc_notify_state_t property_state;
+  apr_array_header_t *propchanges;
+  const svn_wc_conflict_version_t *left_version;
+  const svn_wc_conflict_version_t *right_version;
+  apr_hash_index_t *hi;
+
+  left_version = svn_wc_conflict_version_create2(
+                   repos_root_url, repos_uuid, added_repos_relpath,
+                   merge_left_rev, svn_node_none, scratch_pool);
+
+  right_version = svn_wc_conflict_version_create2(
+                    repos_root_url, repos_uuid, added_repos_relpath,
+                    merge_right_rev, svn_node_dir, scratch_pool);
+
+  propchanges = apr_array_make(scratch_pool, apr_hash_count(added_props),
+                               sizeof(svn_prop_t));
+  for (hi = apr_hash_first(scratch_pool, added_props);
+       hi;
+       hi = apr_hash_next(hi))
+    {
+      svn_prop_t prop;
+
+      prop.name = apr_hash_this_key(hi);
+      prop.value = apr_hash_this_val(hi);
+
+      if (svn_wc_is_normal_prop(prop.name))
+        APR_ARRAY_PUSH(propchanges, svn_prop_t) = prop;
+    }
+
+  SVN_ERR(svn_wc_merge_props3(&property_state, ctx->wc_ctx,
+                              target_abspath,
+                              left_version, right_version,
+                              apr_hash_make(scratch_pool),
+                              propchanges,
+                              FALSE, /* not a dry-run */
+                              NULL, NULL, NULL, NULL,
+                              scratch_pool));
+
+  if (ctx->notify_func2)
+    {
+      svn_wc_notify_t *notify;
+
+      notify = svn_wc_create_notify(target_abspath,
+                                    svn_wc_notify_update_update,
+                                    scratch_pool);
+      notify->kind = svn_node_dir;
+      notify->content_state = svn_wc_notify_state_unchanged;;
+      notify->prop_state = property_state;
+      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+    }
+
+  return SVN_NO_ERROR;
 }
 
-/* Implements conflict_option_resolve_func_t. */
+/* An svn_diff_tree_processor_t callback. */
 static svn_error_t *
-resolve_update_incoming_added_file_replace(svn_client_conflict_option_t *option,
-                                           svn_client_conflict_t *conflict,
-                                           svn_client_ctx_t *ctx,
-                                           apr_pool_t *scratch_pool)
+diff_dir_added(const char *relpath,
+               const svn_diff_source_t *copyfrom_source,
+               const svn_diff_source_t *right_source,
+               apr_hash_t *copyfrom_props,
+               apr_hash_t *right_props,
+               void *dir_baton,
+               const struct svn_diff_tree_processor_t *processor,
+               apr_pool_t *scratch_pool)
 {
-  svn_client_conflict_option_id_t option_id;
+  struct merge_newly_added_dir_baton *b = processor->baton;
   const char *local_abspath;
-  const char *lock_abspath;
-  svn_error_t *err;
-  const char *backup_path;
+  const char *copyfrom_url;
+  svn_node_kind_t db_kind;
+  svn_node_kind_t on_disk_kind;
+  apr_hash_index_t *hi;
 
-  option_id = svn_client_conflict_option_get_id(option);
-  local_abspath = svn_client_conflict_get_local_abspath(conflict);
+  /* Handle the root of the added directory tree. */
+  if (relpath[0] == '\0')
+    {
+      /* ### svn_wc_merge_props3() requires this... */
+      SVN_ERR(svn_wc__del_tree_conflict(b->ctx->wc_ctx, b->target_abspath,
+                                        scratch_pool));
+      SVN_ERR(merge_added_dir_props(b->target_abspath,
+                                    b->added_repos_relpath, right_props,
+                                    b->repos_root_url, b->repos_uuid,
+                                    b->merge_left_rev, b->merge_right_rev,
+                                    b->ctx, scratch_pool));
+      return SVN_NO_ERROR;
+
+    }
+
+  local_abspath = svn_dirent_join(b->target_abspath, relpath, scratch_pool);
+
+  SVN_ERR(svn_wc_read_kind2(&db_kind, b->ctx->wc_ctx, local_abspath,
+                            FALSE, FALSE, scratch_pool));
+  SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool));
+
+  if (db_kind == svn_node_dir && on_disk_kind == svn_node_dir)
+    {
+      SVN_ERR(merge_added_dir_props(svn_dirent_join(b->target_abspath, relpath,
+                                                    scratch_pool),
+                                    b->added_repos_relpath, right_props,
+                                    b->repos_root_url, b->repos_uuid,
+                                    b->merge_left_rev, b->merge_right_rev,
+                                    b->ctx, scratch_pool));
+      return SVN_NO_ERROR;
+    }
+
+  if (db_kind != svn_node_none && db_kind != svn_node_unknown)
+    {
+      SVN_ERR(raise_tree_conflict(
+                local_abspath, svn_wc_conflict_action_add,
+                svn_wc_conflict_reason_obstructed,
+                db_kind, svn_node_none, svn_node_dir,
+                b->repos_root_url, b->repos_uuid,
+                svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
+                b->merge_left_rev, b->merge_right_rev,
+                b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2,
+                scratch_pool));
+      return SVN_NO_ERROR;
+    }
+
+  if (on_disk_kind != svn_node_none)
+    {
+      SVN_ERR(raise_tree_conflict(
+                local_abspath, svn_wc_conflict_action_add,
+                svn_wc_conflict_reason_obstructed, db_kind,
+                svn_node_none, svn_node_dir, b->repos_root_url, b->repos_uuid,
+                svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
+                b->merge_left_rev, b->merge_right_rev,
+                b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2,
+                scratch_pool));
+      return SVN_NO_ERROR;
+    }
+
+  SVN_ERR(svn_io_dir_make(local_abspath, APR_OS_DEFAULT, scratch_pool));
+  copyfrom_url = apr_pstrcat(scratch_pool, b->repos_root_url, "/",
+                             right_source->repos_relpath, SVN_VA_NULL);
+  SVN_ERR(svn_wc_add4(b->ctx->wc_ctx, local_abspath, svn_depth_infinity,
+                      copyfrom_url, right_source->revision,
+                      NULL, NULL, /* cancel func/baton */
+                      b->ctx->notify_func2, b->ctx->notify_baton2,
+                      scratch_pool));
+
+  for (hi = apr_hash_first(scratch_pool, right_props);
+       hi;
+       hi = apr_hash_next(hi))
+    {
+      const char *propname = apr_hash_this_key(hi);
+      const svn_string_t *propval = apr_hash_this_val(hi);
+
+      SVN_ERR(svn_wc_prop_set4(b->ctx->wc_ctx, local_abspath,
+                               propname, propval, svn_depth_empty,
+                               FALSE, NULL /* do not skip checks */,
+                               NULL, NULL, /* cancel func/baton */
+                               b->ctx->notify_func2, b->ctx->notify_baton2,
+                               scratch_pool));
+    }
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+merge_added_files(const char *local_abspath,
+                  const char *incoming_added_file_abspath,
+                  apr_hash_t *incoming_added_file_props,
+                  svn_client_ctx_t *ctx,
+                  apr_pool_t *scratch_pool)
+{
+  svn_wc_merge_outcome_t merge_content_outcome;
+  svn_wc_notify_state_t merge_props_outcome;
+  apr_file_t *empty_file;
+  const char *empty_file_abspath;
+  apr_array_header_t *propdiffs;
+  apr_hash_t *working_props;
+
+  /* Create an empty file as fake "merge-base" for the two added files.
+   * The files are not ancestrally related so this is the best we can do. */
+  SVN_ERR(svn_io_open_unique_file3(&empty_file, &empty_file_abspath, NULL,
+                                   svn_io_file_del_on_pool_cleanup,
+                                   scratch_pool, scratch_pool));
+
+  /* Get a copy of the working file's properties. */
+  SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath,
+                            scratch_pool, scratch_pool));
+
+  /* Create a property diff for the files. */
+  SVN_ERR(svn_prop_diffs(&propdiffs, incoming_added_file_props,
+                         working_props, scratch_pool));
+
+  /* Perform the file merge. */
+  SVN_ERR(svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
+                        ctx->wc_ctx, empty_file_abspath,
+                        incoming_added_file_abspath, local_abspath,
+                        NULL, NULL, NULL, /* labels */
+                        NULL, NULL, /* conflict versions */
+                        FALSE, /* dry run */
+                        NULL, NULL, /* diff3_cmd, merge_options */
+                        NULL, propdiffs,
+                        NULL, NULL, /* conflict func/baton */
+                        NULL, NULL, /* don't allow user to cancel here */
+                        scratch_pool));
+
+  if (ctx->notify_func2)
+    {
+      svn_wc_notify_t *notify = svn_wc_create_notify(
+                                   local_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);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* An svn_diff_tree_processor_t callback. */
+static svn_error_t *
+diff_file_added(const char *relpath,
+                const svn_diff_source_t *copyfrom_source,
+                const svn_diff_source_t *right_source,
+                const char *copyfrom_file,
+                const char *right_file,
+                apr_hash_t *copyfrom_props,
+                apr_hash_t *right_props,
+                void *file_baton,
+                const struct svn_diff_tree_processor_t *processor,
+                apr_pool_t *scratch_pool)
+{
+  struct merge_newly_added_dir_baton *b = processor->baton;
+  const char *local_abspath;
+  svn_node_kind_t db_kind;
+  svn_node_kind_t on_disk_kind;
+  apr_array_header_t *propsarray;
+  apr_array_header_t *regular_props;
+
+  local_abspath = svn_dirent_join(b->target_abspath, relpath, scratch_pool);
+
+  SVN_ERR(svn_wc_read_kind2(&db_kind, b->ctx->wc_ctx, local_abspath,
+                            FALSE, FALSE, scratch_pool));
+  SVN_ERR(svn_io_check_path(local_abspath, &on_disk_kind, scratch_pool));
+
+  if (db_kind == svn_node_file && on_disk_kind == svn_node_file)
+    {
+      propsarray = svn_prop_hash_to_array(right_props, scratch_pool);
+      SVN_ERR(svn_categorize_props(propsarray, NULL, NULL, &regular_props,
+                                   scratch_pool));
+      SVN_ERR(merge_added_files(local_abspath, right_file,
+                                svn_prop_array_to_hash(regular_props,
+                                                       scratch_pool),
+                                b->ctx, scratch_pool));
+      return SVN_NO_ERROR;
+    }
 
-  /* ### The following WC modifications should be atomic. */
-  SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
-                                                 local_abspath,
-                                                 scratch_pool, scratch_pool));
+  if (db_kind != svn_node_none && db_kind != svn_node_unknown)
+    {
+      SVN_ERR(raise_tree_conflict(
+                local_abspath, svn_wc_conflict_action_add,
+                svn_wc_conflict_reason_obstructed,
+                db_kind, svn_node_none, svn_node_file,
+                b->repos_root_url, b->repos_uuid,
+                svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
+                b->merge_left_rev, b->merge_right_rev,
+                b->ctx->wc_ctx, b->ctx->notify_func2, b->ctx->notify_baton2,
+                scratch_pool));
+      return SVN_NO_ERROR;
+    }
 
-  err = verify_local_state_for_incoming_add_upon_update(conflict, option, ctx,
-                                                        scratch_pool);
-  if (err)
-    goto unlock_wc;
+  if (on_disk_kind != svn_node_none)
+    {
+      SVN_ERR(raise_tree_conflict(
+                local_abspath, svn_wc_conflict_action_add,
+                svn_wc_conflict_reason_obstructed, db_kind,
+                svn_node_none, svn_node_file, b->repos_root_url, b->repos_uuid,
+                svn_relpath_join(b->added_repos_relpath, relpath, scratch_pool),
+                b->merge_left_rev, b->merge_right_rev,

[... 1809 lines stripped ...]


Mime
View raw message