subversion-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From julianf...@apache.org
Subject svn commit: r1813858 [2/8] - in /subversion/branches/shelve-checkpoint3: ./ build/ build/generator/ notes/commit-access-templates/ subversion/bindings/javahl/native/ subversion/bindings/swig/include/ subversion/include/ subversion/include/private/ subv...
Date Tue, 31 Oct 2017 09:36:54 GMT
Modified: subversion/branches/shelve-checkpoint3/subversion/libsvn_client/conflicts.c
URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint3/subversion/libsvn_client/conflicts.c?rev=1813858&r1=1813857&r2=1813858&view=diff
==============================================================================
--- subversion/branches/shelve-checkpoint3/subversion/libsvn_client/conflicts.c (original)
+++ subversion/branches/shelve-checkpoint3/subversion/libsvn_client/conflicts.c Tue Oct 31 09:36:53 2017
@@ -267,6 +267,9 @@ struct repos_move_info {
   /* The copyfrom revision of the moved-to path. */
   svn_revnum_t copyfrom_rev;
 
+  /* The node kind of the item being moved. */
+  svn_node_kind_t node_kind;
+
   /* Prev pointer. NULL if no prior move exists in the chain. */
   struct repos_move_info *prev;
 
@@ -366,6 +369,7 @@ struct copy_info {
   const char *copyto_path;
   const char *copyfrom_path;
   svn_revnum_t copyfrom_rev;
+  svn_node_kind_t node_kind;
 };
 
 /* Allocate and return a NEW_MOVE, and update MOVED_PATHS with this new move. */
@@ -374,6 +378,7 @@ add_new_move(struct repos_move_info **ne
              const char *deleted_repos_relpath,
              const char *copyto_path,
              svn_revnum_t copyfrom_rev,
+             svn_node_kind_t node_kind,
              svn_revnum_t revision,
              const char *author,
              apr_hash_t *moved_paths,
@@ -392,6 +397,7 @@ add_new_move(struct repos_move_info **ne
   move->rev = revision;
   move->rev_author = apr_pstrdup(result_pool, author);
   move->copyfrom_rev = copyfrom_rev;
+  move->node_kind = node_kind;
 
   /* Link together multiple moves of the same node.
    * Note that we're traversing history backwards, so moves already
@@ -430,6 +436,269 @@ add_new_move(struct repos_move_info **ne
   return SVN_NO_ERROR;
 }
 
+/* Push a MOVE into the MOVES_TABLE. */
+static void
+push_move(struct repos_move_info *move, apr_hash_t *moves_table,
+          apr_pool_t *result_pool)
+{
+  apr_array_header_t *moves;
+
+  /* Add this move to the list of moves in the revision. */
+  moves = apr_hash_get(moves_table, &move->rev, sizeof(svn_revnum_t));
+  if (moves == NULL)
+    {
+      /* It 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;
+}
+
+/* Find the youngest common ancestor of REPOS_RELPATH1@PEG_REV1 and
+ * REPOS_RELPATH2@PEG_REV2. Return the result in *YCA_LOC.
+ * Set *YCA_LOC to NULL if no common ancestor exists. */
+static svn_error_t *
+find_yca(svn_client__pathrev_t **yca_loc,
+         const char *repos_relpath1,
+         svn_revnum_t peg_rev1,
+         const char *repos_relpath2,
+         svn_revnum_t peg_rev2,
+         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)
+{
+  svn_client__pathrev_t *loc1;
+  svn_client__pathrev_t *loc2;
+
+  *yca_loc = NULL;
+
+  loc1 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid,
+                                                 peg_rev1, repos_relpath1,
+                                                 scratch_pool);
+  loc2 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid,
+                                                 peg_rev2, repos_relpath2,
+                                                 scratch_pool);
+  SVN_ERR(svn_client__get_youngest_common_ancestor(yca_loc, loc1, loc2,
+                                                   ra_session, ctx,
+                                                   result_pool, scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+/* Like find_yca, expect that a YCA could also be found via a brute-force
+ * search of parents of REPOS_RELPATH1 and REPOS_RELPATH2, if no "direct"
+ * YCA exists. An implicit assumption is that some parent of REPOS_RELPATH1
+ * is a branch of some parent of REPOS_RELPATH2.
+ *
+ * This function can guess a "good enough" YCA for 'missing nodes' which do
+ * not exist in the working copy, e.g. when a file edit is merged to a path
+ * which does not exist in the working copy.
+ */
+static svn_error_t *
+find_nearest_yca(svn_client__pathrev_t **yca_locp,
+                 const char *repos_relpath1,
+                 svn_revnum_t peg_rev1,
+                 const char *repos_relpath2,
+                 svn_revnum_t peg_rev2,
+                 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)
+{
+  svn_client__pathrev_t *yca_loc;
+  svn_error_t *err;
+  apr_pool_t *iterpool;
+  const char *p1, *p2;
+  apr_size_t c1, c2;
+
+  *yca_locp = NULL;
+
+  iterpool = svn_pool_create(scratch_pool);
+
+  p1 = repos_relpath1;
+  c1 = svn_path_component_count(repos_relpath1);
+  while (c1--)
+    {
+      svn_pool_clear(iterpool);
+
+      p2 = repos_relpath2;
+      c2 = svn_path_component_count(repos_relpath2);
+      while (c2--)
+        {
+          err = find_yca(&yca_loc, p1, peg_rev1, p2, peg_rev2,
+                         repos_root_url, repos_uuid, ra_session, ctx,
+                         result_pool, iterpool);
+          if (err)
+            {
+              if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+                {
+                  svn_error_clear(err);
+                  yca_loc = NULL;
+                }
+              else
+                return svn_error_trace(err);
+            }
+
+          if (yca_loc)
+            {
+              *yca_locp = yca_loc;
+              svn_pool_destroy(iterpool);
+              return SVN_NO_ERROR;
+            }
+
+          p2 = svn_relpath_dirname(p2, scratch_pool);
+        }
+
+      p1 = svn_relpath_dirname(p1, scratch_pool);
+    }
+
+  svn_pool_destroy(iterpool);
+
+  return SVN_NO_ERROR;
+}
+
+/* Check if the copied node described by COPY and the DELETED_PATH@DELETED_REV
+ * share a common ancestor. If so, return new repos_move_info in *MOVE which
+ * describes a move from the deleted path to that copy's destination. */
+static svn_error_t *
+find_related_move(struct repos_move_info **move,
+                  struct copy_info *copy,
+                  const char *deleted_repos_relpath,
+                  svn_revnum_t deleted_rev,
+                  const char *author,
+                  apr_hash_t *moved_paths,
+                  const char *repos_root_url,
+                  const char *repos_uuid,
+                  svn_client_ctx_t *ctx,
+                  svn_ra_session_t *ra_session,
+                  apr_pool_t *result_pool,
+                  apr_pool_t *scratch_pool)
+{
+  svn_client__pathrev_t *yca_loc;
+  svn_error_t *err;
+
+  *move = NULL;
+  err = find_yca(&yca_loc, copy->copyfrom_path, copy->copyfrom_rev,
+                 deleted_repos_relpath, rev_below(deleted_rev),
+                 repos_root_url, repos_uuid, ra_session, ctx,
+                 scratch_pool, scratch_pool);
+  if (err)
+    {
+      if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+        {
+          svn_error_clear(err);
+          yca_loc = NULL;
+        }
+      else
+        return svn_error_trace(err);
+    }
+
+  if (yca_loc)
+    SVN_ERR(add_new_move(move, deleted_repos_relpath,
+                         copy->copyto_path, copy->copyfrom_rev,
+                         copy->node_kind, deleted_rev, author,
+                         moved_paths, ra_session, repos_root_url,
+                         result_pool, scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+/* Detect moves by matching DELETED_REPOS_RELPATH@DELETED_REV to the copies
+ * in COPIES. Add any moves found to MOVES_TABLE and update MOVED_PATHS. */
+static svn_error_t *
+match_copies_to_deletion(const char *deleted_repos_relpath,
+                         svn_revnum_t deleted_rev,
+                         const char *author,
+                         apr_hash_t *copies,
+                         apr_hash_t *moves_table,
+                         apr_hash_t *moved_paths,
+                         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)
+{
+  apr_hash_index_t *hi;
+  apr_pool_t *iterpool;
+
+  iterpool = svn_pool_create(scratch_pool);
+  for (hi = apr_hash_first(scratch_pool, copies);
+       hi != NULL;
+       hi = apr_hash_next(hi))
+    {
+      const char *copyfrom_path = apr_hash_this_key(hi);
+      apr_array_header_t *copies_with_same_source_path;
+      int i;
+
+      svn_pool_clear(iterpool);
+
+      copies_with_same_source_path = apr_hash_this_val(hi);
+
+      if (strcmp(copyfrom_path, deleted_repos_relpath) == 0)
+        {
+          /* We found a copyfrom path which matches a deleted node.
+           * Check if the deleted node is an ancestor of the copied node. */
+          for (i = 0; i < copies_with_same_source_path->nelts; i++)
+            {
+              struct copy_info *copy;
+              svn_boolean_t related;
+              struct repos_move_info *move;
+
+              copy = APR_ARRAY_IDX(copies_with_same_source_path, i,
+                                   struct copy_info *);
+              SVN_ERR(check_move_ancestry(&related,
+                                          ra_session, repos_root_url,
+                                          deleted_repos_relpath,
+                                          deleted_rev,
+                                          copy->copyfrom_path,
+                                          copy->copyfrom_rev,
+                                          TRUE, iterpool));
+              if (!related)
+                continue;
+              
+              /* Remember details of this move. */
+              SVN_ERR(add_new_move(&move, deleted_repos_relpath,
+                                   copy->copyto_path, copy->copyfrom_rev,
+                                   copy->node_kind, deleted_rev, author,
+                                   moved_paths, ra_session, repos_root_url,
+                                   result_pool, iterpool));
+              push_move(move, moves_table, result_pool);
+            } 
+        }
+      else
+        {
+          /* Check if this deleted node is related to any copies in this
+           * revision. These could be moves of the deleted node which
+           * were merged here from other lines of history. */
+          for (i = 0; i < copies_with_same_source_path->nelts; i++)
+            {
+              struct copy_info *copy;
+              struct repos_move_info *move = NULL;
+
+              copy = APR_ARRAY_IDX(copies_with_same_source_path, i,
+                                   struct copy_info *);
+              SVN_ERR(find_related_move(&move, copy, deleted_repos_relpath,
+                                        deleted_rev, author,
+                                        moved_paths,
+                                        repos_root_url, repos_uuid,
+                                        ctx, ra_session,
+                                        result_pool, iterpool));
+              if (move)
+                push_move(move, moves_table, result_pool);
+            }
+        }
+    }
+  svn_pool_destroy(iterpool);
+
+  return SVN_NO_ERROR;
+}
+
 /* Update MOVES_TABLE and MOVED_PATHS based on information from
  * revision data in LOG_ENTRY, COPIES, and DELETED_PATHS.
  * Use RA_SESSION to perform the necessary requests. */
@@ -441,73 +710,31 @@ find_moves_in_revision(svn_ra_session_t
                        apr_hash_t *copies,
                        apr_array_header_t *deleted_paths,
                        const char *repos_root_url,
+                       const char *repos_uuid,
+                       svn_client_ctx_t *ctx,
                        apr_pool_t *result_pool,
                        apr_pool_t *scratch_pool)
 {
   apr_pool_t *iterpool;
-  svn_boolean_t related;
   int i;
+  const svn_string_t *author;
 
+  author = svn_hash_gets(log_entry->revprops, SVN_PROP_REVISION_AUTHOR);
   iterpool = svn_pool_create(scratch_pool);
   for (i = 0; i < deleted_paths->nelts; i++)
     {
       const char *deleted_repos_relpath;
-      struct repos_move_info *move;
-      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 *);
-
-      copies_with_same_source_path = svn_hash_gets(copies,
-                                                   deleted_repos_relpath);
-      if (copies_with_same_source_path == NULL)
-        continue; /* Not a move, or a nested move we handle later on. */
-
-      for (j = 0; j < copies_with_same_source_path->nelts; j++)
-        {
-          struct copy_info *copy;
-
-          /* We found a copy with a copyfrom path which matches a deleted node.
-           * Verify that the deleted node is an ancestor of the copied node. */
-          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,
-                                      TRUE, iterpool));
-          if (related)
-            {
-              const svn_string_t *author;
-              
-              author = svn_hash_gets(log_entry->revprops,
-                                     SVN_PROP_REVISION_AUTHOR);
-              /* Remember details of this move. */
-              SVN_ERR(add_new_move(&move, deleted_repos_relpath,
-                                   copy->copyto_path, copy->copyfrom_rev,
-                                   log_entry->revision,
-                                   author ? author->data : _("unknown author"),
-                                   moved_paths, ra_session, repos_root_url,
-                                   result_pool, iterpool));
-
-              /* 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)
-                {
-                  /* It 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_ERR(match_copies_to_deletion(deleted_repos_relpath,
+                                       log_entry->revision,
+                                       author ? author->data
+                                              : _("unknown author"),
+                                       copies, moves_table, moved_paths,
+                                       repos_root_url, repos_uuid, ra_session,
+                                       ctx, result_pool, iterpool));
     }
   svn_pool_destroy(iterpool);
 
@@ -520,7 +747,7 @@ struct find_deleted_rev_baton
    * svn_ra_get_log2(). */
   const char *deleted_repos_relpath;
   const char *related_repos_relpath;
-  svn_revnum_t related_repos_peg_rev;
+  svn_revnum_t related_peg_rev;
   const char *repos_root_url;
   const char *repos_uuid;
   svn_client_ctx_t *ctx;
@@ -539,40 +766,6 @@ struct find_deleted_rev_baton
   svn_ra_session_t *extra_ra_session;
 };
 
-/* Find the youngest common ancestor of REPOS_RELPATH1@PEG_REV1 and
- * REPOS_RELPATH2@PEG_REV2. Return the result in *YCA_LOC.
- * Set *YCA_LOC to NULL if no common ancestor exists. */
-static svn_error_t *
-find_yca(svn_client__pathrev_t **yca_loc,
-         const char *repos_relpath1,
-         svn_revnum_t peg_rev1,
-         const char *repos_relpath2,
-         svn_revnum_t peg_rev2,
-         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)
-{
-  svn_client__pathrev_t *loc1;
-  svn_client__pathrev_t *loc2;
-
-  *yca_loc = NULL;
-
-  loc1 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid,
-                                                 peg_rev1, repos_relpath1,
-                                                 scratch_pool);
-  loc2 = svn_client__pathrev_create_with_relpath(repos_root_url, repos_uuid,
-                                                 peg_rev2, repos_relpath2,
-                                                 scratch_pool);
-  SVN_ERR(svn_client__get_youngest_common_ancestor(yca_loc, loc1, loc2,
-                                                   ra_session, ctx,
-                                                   result_pool, scratch_pool));
-
-  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. */
@@ -708,6 +901,7 @@ find_nested_moves(apr_array_header_t *mo
               /* Remember details of this move. */
               SVN_ERR(add_new_move(&nested_move, moved_along_repos_relpath,
                                    copy->copyto_path, copy->copyfrom_rev,
+                                   copy->node_kind,
                                    revision, author, moved_paths,
                                    ra_session, repos_root_url,
                                    result_pool, iterpool));
@@ -726,6 +920,34 @@ find_nested_moves(apr_array_header_t *mo
   return SVN_NO_ERROR;
 }
 
+/* Make a shallow copy of the copied LOG_ITEM in COPIES. */
+static void
+cache_copied_item(apr_hash_t *copies, const char *changed_path,
+                  svn_log_changed_path2_t *log_item)
+{
+  apr_pool_t *result_pool = apr_hash_pool_get(copies);
+  struct copy_info *copy = apr_palloc(result_pool, sizeof(*copy));
+  apr_array_header_t *copies_with_same_source_path;
+
+  copy->copyfrom_path = log_item->copyfrom_path;
+  if (log_item->copyfrom_path[0] == '/')
+    copy->copyfrom_path++;
+  copy->copyto_path = changed_path;
+  copy->copyfrom_rev = log_item->copyfrom_rev;
+  copy->node_kind = log_item->node_kind;
+
+  copies_with_same_source_path = apr_hash_get(copies, copy->copyfrom_path,
+                                              APR_HASH_KEY_STRING);
+  if (copies_with_same_source_path == NULL)
+    {
+      copies_with_same_source_path = apr_array_make(result_pool, 1,
+                                                    sizeof(struct copy_info *));
+      apr_hash_set(copies, copy->copyfrom_path, APR_HASH_KEY_STRING,
+                   copies_with_same_source_path);
+    }
+  APR_ARRAY_PUSH(copies_with_same_source_path, struct copy_info *) = copy;
+}
+
 /* Implements svn_log_entry_receiver_t.
  *
  * Find the revision in which a node, optionally ancestrally related to the
@@ -796,7 +1018,7 @@ find_deleted_rev(void *baton,
           deleted_node_found = TRUE;
 
           if (b->related_repos_relpath != NULL &&
-              b->related_repos_peg_rev != SVN_INVALID_REVNUM)
+              b->related_peg_rev != SVN_INVALID_REVNUM)
             {
               svn_client__pathrev_t *yca_loc;
               svn_error_t *err;
@@ -807,7 +1029,7 @@ find_deleted_rev(void *baton,
                * "related node" specified in our baton. */
               err = find_yca(&yca_loc,
                              b->related_repos_relpath,
-                             b->related_repos_peg_rev,
+                             b->related_peg_rev,
                              b->deleted_repos_relpath,
                              rev_below(log_entry->revision),
                              b->repos_root_url, b->repos_uuid,
@@ -1438,31 +1660,7 @@ find_moves(void *baton, svn_log_entry_t
 
       /* For move detection, scan for copied nodes in this revision. */
       if (log_item->action == 'A' && log_item->copyfrom_path)
-        {
-          struct copy_info *copy;
-          apr_array_header_t *copies_with_same_source_path;
-
-          if (log_item->copyfrom_path[0] == '/')
-            log_item->copyfrom_path++;
-
-          copy = apr_palloc(scratch_pool, sizeof(*copy));
-          copy->copyto_path = changed_path;
-          copy->copyfrom_path = log_item->copyfrom_path;
-          copy->copyfrom_rev = log_item->copyfrom_rev;
-          copies_with_same_source_path = apr_hash_get(copies,
-                                                      log_item->copyfrom_path,
-                                                      APR_HASH_KEY_STRING);
-          if (copies_with_same_source_path == NULL)
-            {
-              copies_with_same_source_path = apr_array_make(
-                                               scratch_pool, 1,
-                                               sizeof(struct copy_info *));
-              apr_hash_set(copies, copy->copyfrom_path, APR_HASH_KEY_STRING,
-                           copies_with_same_source_path);
-            }
-          APR_ARRAY_PUSH(copies_with_same_source_path,
-                         struct copy_info *) = copy;
-        }
+        cache_copied_item(copies, changed_path, log_item);
 
       /* For move detection, store all deleted_paths. */
       if (log_item->action == 'D' || log_item->action == 'R')
@@ -1475,8 +1673,8 @@ find_moves(void *baton, svn_log_entry_t
   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->result_pool, scratch_pool));
+                                 b->repos_root_url, b->repos_uuid,
+                                 b->ctx, b->result_pool, scratch_pool));
 
   moves = apr_hash_get(b->moves_table, &log_entry->revision,
                        sizeof(svn_revnum_t));
@@ -1503,7 +1701,9 @@ find_moves(void *baton, svn_log_entry_t
 static svn_error_t *
 find_moves_in_revision_range(struct apr_hash_t **moves_table,
                              const char *repos_relpath,
-                             svn_client_conflict_t *conflict,
+                             const char *repos_root_url,
+                             const char *repos_uuid,
+                             const char *victim_abspath,
                              svn_revnum_t start_rev,
                              svn_revnum_t end_rev,
                              svn_client_ctx_t *ctx,
@@ -1515,15 +1715,10 @@ find_moves_in_revision_range(struct apr_
   const char *corrected_url;
   apr_array_header_t *paths;
   apr_array_header_t *revprops;
-  const char *repos_root_url;
-  const char *repos_uuid;
   struct find_moves_baton b = { 0 };
 
   SVN_ERR_ASSERT(start_rev > end_rev);
 
-  SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid,
-                                             conflict, scratch_pool,
-                                             scratch_pool));
   url = svn_path_url_add_component2(repos_root_url, repos_relpath,
                                     scratch_pool);
   SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
@@ -1540,7 +1735,7 @@ find_moves_in_revision_range(struct apr_
   b.repos_root_url = repos_root_url;
   b.repos_uuid = repos_uuid;
   b.ctx = ctx;
-  b.victim_abspath = conflict->local_abspath;
+  b.victim_abspath = victim_abspath;
   b.moves_table = apr_hash_make(result_pool);
   b.moved_paths = apr_hash_make(scratch_pool);
   b.result_pool = result_pool;
@@ -1562,12 +1757,14 @@ find_moves_in_revision_range(struct apr_
 }
 
 /* Return new move information for a moved-along child MOVED_ALONG_RELPATH.
+ * Set MOVE->NODE_KIND to MOVED_ALONG_NODE_KIND.
  * Do not copy MOVE->NEXT and MOVE-PREV.
  * If MOVED_ALONG_RELPATH is empty, this effectively copies MOVE to
  * RESULT_POOL with NEXT and PREV pointers cleared. */
 static struct repos_move_info *
 new_path_adjusted_move(struct repos_move_info *move,
                        const char *moved_along_relpath,
+                       svn_node_kind_t moved_along_node_kind,
                        apr_pool_t *result_pool)
 {
   struct repos_move_info *new_move;
@@ -1582,6 +1779,7 @@ new_path_adjusted_move(struct repos_move
   new_move->rev = move->rev;
   new_move->rev_author = apr_pstrdup(result_pool, move->rev_author);
   new_move->copyfrom_rev = move->copyfrom_rev;
+  new_move->node_kind = moved_along_node_kind;
   /* Ignore prev and next pointers. Caller will set them if needed. */
 
   return new_move;
@@ -1644,7 +1842,8 @@ find_next_moves_in_revision(apr_array_he
           struct repos_move_info *new_move;
 
           /* We have a winner. */
-          new_move = new_path_adjusted_move(move, relpath, result_pool);
+          new_move = new_path_adjusted_move(move, relpath, prev_move->node_kind,
+                                            result_pool);
           if (*next_moves == NULL)
             *next_moves = apr_array_make(result_pool, 1,
                                          sizeof(struct repos_move_info *));
@@ -1743,6 +1942,188 @@ trace_moved_node(apr_hash_t *moves_table
   return SVN_NO_ERROR;
 }
 
+/* Given a list of MOVES_IN_REVISION, figure out which of these moves
+ * move the node which was later on moved by NEXT_MOVE. */
+static svn_error_t *
+find_prev_move_in_revision(struct repos_move_info **prev_move,
+                           apr_array_header_t *moves_in_revision,
+                           struct repos_move_info *next_move,
+                           svn_ra_session_t *ra_session,
+                           const char *repos_root_url,
+                           apr_pool_t *result_pool,
+                           apr_pool_t *scratch_pool)
+{
+  int i;
+  apr_pool_t *iterpool;
+
+  *prev_move = NULL;
+
+  iterpool = svn_pool_create(scratch_pool);
+  for (i = 0; i < moves_in_revision->nelts; i++)
+    {
+      struct repos_move_info *move;
+      const char *relpath;
+      const char *deleted_repos_relpath;
+      svn_boolean_t related;
+      svn_error_t *err;
+
+      svn_pool_clear(iterpool);
+
+      /* Check if this move affects the current known path of our node. */
+      move = APR_ARRAY_IDX(moves_in_revision, i, struct repos_move_info *);
+      relpath = svn_relpath_skip_ancestor(next_move->moved_from_repos_relpath,
+                                          move->moved_to_repos_relpath);
+      if (relpath == NULL)
+        continue;
+
+      /* It does. So our node must have been deleted. */
+      deleted_repos_relpath = svn_relpath_join(
+                                next_move->moved_from_repos_relpath,
+                                relpath, iterpool);
+
+      /* 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. */
+      err = check_move_ancestry(&related, ra_session, repos_root_url,
+                                deleted_repos_relpath, next_move->rev,
+                                move->moved_from_repos_relpath,
+                                move->copyfrom_rev,
+                                FALSE, scratch_pool);
+      if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+        {
+          svn_error_clear(err);
+          continue;
+        }
+      else
+        SVN_ERR(err);
+
+      if (related)
+        {
+          /* We have a winner. */
+          *prev_move = new_path_adjusted_move(move, relpath,
+                                              next_move->node_kind,
+                                              result_pool);
+          break;
+        }
+    }
+  svn_pool_destroy(iterpool);
+
+  return SVN_NO_ERROR;
+}
+
+static int
+compare_items_as_revs_reverse(const svn_sort__item_t *a,
+                              const svn_sort__item_t *b)
+{
+  int c = svn_sort_compare_revisions(a->key, b->key);
+  if (c < 0)
+    return 1;
+  if (c > 0)
+    return -1;
+  return c;
+}
+
+/* Starting at MOVE->REV, loop over past revisions which contain moves,
+ * and look for a matching previous move in each. Once found, return
+ * it in *PREV_MOVE */
+static svn_error_t *
+find_prev_move(struct repos_move_info **prev_move,
+               apr_hash_t *moves_table,
+               struct repos_move_info *move,
+               svn_ra_session_t *ra_session,
+               const char *repos_root_url,
+               apr_pool_t *result_pool,
+               apr_pool_t *scratch_pool)
+{
+  apr_array_header_t *moves;
+  apr_array_header_t *revisions;
+  apr_pool_t *iterpool;
+  int i;
+
+  *prev_move = NULL;
+  revisions = svn_sort__hash(moves_table, compare_items_as_revs_reverse,
+                             scratch_pool);
+  iterpool = svn_pool_create(scratch_pool);
+  for (i = 0; i < revisions->nelts; i++)
+    {
+      svn_sort__item_t item = APR_ARRAY_IDX(revisions, i, svn_sort__item_t);
+      svn_revnum_t rev = *(svn_revnum_t *)item.key;
+
+      svn_pool_clear(iterpool);
+
+      if (rev >= move->rev)
+        continue;
+
+      moves = apr_hash_get(moves_table, &rev, sizeof(rev));
+      SVN_ERR(find_prev_move_in_revision(prev_move, moves, move,
+                                         ra_session, repos_root_url,
+                                         result_pool, iterpool));
+      if (*prev_move)
+        break;
+    }
+  svn_pool_destroy(iterpool);
+
+  return SVN_NO_ERROR;
+}
+
+
+/* Trace all past moves of the node moved by MOVE.
+ * Update MOVE->PREV and MOVE->NEXT accordingly. */
+static svn_error_t *
+trace_moved_node_backwards(apr_hash_t *moves_table,
+                           struct repos_move_info *move,
+                           svn_ra_session_t *ra_session,
+                           const char *repos_root_url,
+                           apr_pool_t *result_pool,
+                           apr_pool_t *scratch_pool)
+{
+  struct repos_move_info *prev_move;
+
+  SVN_ERR(find_prev_move(&prev_move, moves_table, move,
+                         ra_session, repos_root_url,
+                         result_pool, scratch_pool));
+  if (prev_move)
+    {
+      move->prev = prev_move;
+      prev_move->next = apr_array_make(result_pool, 1,
+                                       sizeof(struct repos_move_info *));
+      APR_ARRAY_PUSH(prev_move->next, struct repos_move_info *) = move;
+
+      SVN_ERR(trace_moved_node_backwards(moves_table, prev_move,
+                                         ra_session, repos_root_url,
+                                         result_pool, scratch_pool));
+    }
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+reparent_session_and_fetch_node_kind(svn_node_kind_t *node_kind,
+                                     svn_ra_session_t *ra_session,
+                                     const char *url,
+                                     svn_revnum_t peg_rev,
+                                     apr_pool_t *scratch_pool)
+{
+  svn_error_t *err;
+
+  err = svn_ra_reparent(ra_session, url, scratch_pool);
+  if (err)
+    {
+      if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
+        {
+          svn_error_clear(err);
+          *node_kind = svn_node_unknown;
+          return SVN_NO_ERROR;
+        }
+    
+      return svn_error_trace(err);
+    }
+
+  SVN_ERR(svn_ra_check_path(ra_session, "", peg_rev, node_kind, scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
 /* Scan MOVES_TABLE for moves which affect a particular deleted node, and
  * build a set of new move information for this node.
  * Return heads of all possible move chains in *MOVES.
@@ -1766,6 +2147,7 @@ find_operative_moves(apr_array_header_t
   apr_array_header_t *moves_in_deleted_rev;
   int i;
   apr_pool_t *iterpool;
+  const char *session_url, *url = NULL;
 
   moves_in_deleted_rev = apr_hash_get(moves_table, &deleted_rev,
                                       sizeof(deleted_rev));
@@ -1775,6 +2157,8 @@ find_operative_moves(apr_array_header_t
       return SVN_NO_ERROR;
     }
 
+  SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, scratch_pool));
+
   /* Look for operative moves in the revision where the node was deleted. */
   *moves = apr_array_make(scratch_pool, 0, sizeof(struct repos_move_info *));
   iterpool = svn_pool_create(scratch_pool);
@@ -1788,15 +2172,25 @@ find_operative_moves(apr_array_header_t
       move = APR_ARRAY_IDX(moves_in_deleted_rev, i, struct repos_move_info *);
       relpath = svn_relpath_skip_ancestor(move->moved_from_repos_relpath,
                                           deleted_repos_relpath);
-      if (relpath)
+      if (relpath && relpath[0] != '\0')
         {
-          struct repos_move_info *new_move;
+          svn_node_kind_t node_kind;
 
-          new_move = new_path_adjusted_move(move, relpath, result_pool);
-          APR_ARRAY_PUSH(*moves, struct repos_move_info *) = new_move;
+          url = svn_path_url_add_component2(repos_root_url,
+                                            deleted_repos_relpath,
+                                            iterpool);
+          SVN_ERR(reparent_session_and_fetch_node_kind(&node_kind,
+                                                       ra_session, url,
+                                                       rev_below(deleted_rev),
+                                                       iterpool));
+          move = new_path_adjusted_move(move, relpath, node_kind, result_pool);
         }
+      APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move;
     }
 
+  if (url != NULL)
+    SVN_ERR(svn_ra_reparent(ra_session, session_url, scratch_pool));
+
   /* If we didn't find any applicable moves, return NULL. */
   if ((*moves)->nelts == 0)
     {
@@ -1862,13 +2256,16 @@ find_revision_for_suspected_deletion(svn
 
   SVN_ERR_ASSERT(start_rev > end_rev);
 
-  SVN_ERR(find_moves_in_revision_range(&moves_table, parent_repos_relpath,
-                                       conflict, start_rev, end_rev,
-                                       ctx, result_pool, scratch_pool));
-
   SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid,
                                              conflict, scratch_pool,
                                              scratch_pool));
+  victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+
+  SVN_ERR(find_moves_in_revision_range(&moves_table, parent_repos_relpath,
+                                       repos_root_url, repos_uuid,
+                                       victim_abspath, start_rev, end_rev,
+                                       ctx, result_pool, scratch_pool));
+
   url = svn_path_url_add_component2(repos_root_url, parent_repos_relpath,
                                     scratch_pool);
   SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
@@ -1882,12 +2279,11 @@ find_revision_for_suspected_deletion(svn
   revprops = apr_array_make(scratch_pool, 1, sizeof(const char *));
   APR_ARRAY_PUSH(revprops, const char *) = SVN_PROP_REVISION_AUTHOR;
 
-  victim_abspath = svn_client_conflict_get_local_abspath(conflict);
   b.victim_abspath = victim_abspath;
   b.deleted_repos_relpath = svn_relpath_join(parent_repos_relpath,
                                              deleted_basename, scratch_pool);
   b.related_repos_relpath = related_repos_relpath;
-  b.related_repos_peg_rev = related_peg_rev;
+  b.related_peg_rev = related_peg_rev;
   b.deleted_rev = SVN_INVALID_REVNUM;
   b.replacing_node_kind = svn_node_unknown;
   b.repos_root_url = repos_root_url;
@@ -1929,7 +2325,7 @@ find_revision_for_suspected_deletion(svn
           *deleted_rev_author = move->rev_author;
           *replacing_node_kind = b.replacing_node_kind;
           SVN_ERR(find_operative_moves(moves, moves_table,
-                                       move->moved_from_repos_relpath,
+                                       b.deleted_repos_relpath,
                                        move->rev,
                                        ra_session, repos_root_url,
                                        result_pool, scratch_pool));
@@ -1954,7 +2350,6 @@ find_revision_for_suspected_deletion(svn
                                    b.deleted_repos_relpath, b.deleted_rev,
                                    ra_session, repos_root_url,
                                    result_pool, scratch_pool));
-
     }
 
   return SVN_NO_ERROR;
@@ -1972,11 +2367,20 @@ struct conflict_tree_local_missing_detai
   /* The path which was deleted relative to the repository root. */
   const char *deleted_repos_relpath;
 
-  /* Move information. If not NULL, this is an array of repos_move_info *
-   * elements. Each element is the head of a move chain which starts in
-   * DELETED_REV. */
+  /* Move information about the conflict victim. If not NULL, this is an
+   * array of repos_move_info elements. Each element is the head of a
+   * move chain which starts in DELETED_REV. */
   apr_array_header_t *moves;
 
+  /* Move information about siblings. Siblings are nodes which share
+   * a youngest common ancestor with the conflict victim. E.g. in case
+   * of a merge operation they are part of the merge source branch.
+   * If not NULL, this is an array of repos_move_info elements.
+   * Each element is the head of a move chain, which starts at some
+   * point in history after siblings and conflict victim forked off
+   * their common ancestor. */
+  apr_array_header_t *sibling_moves;
+
   /* If not NULL, this is the move target abspath. */
   const char *moved_to_abspath;
 };
@@ -2063,6 +2467,140 @@ find_related_node(const char **related_r
   return SVN_NO_ERROR;
 }
 
+/* Determine if REPOS_RELPATH@PEG_REV was moved at some point in its history.
+ * History's range of interest ends at END_REV which must be older than PEG_REV.
+ *
+ * VICTIM_ABSPATH is the abspath of a conflict victim in the working copy and
+ * will be used in notifications.
+ *
+ * Return any applicable move chain heads in *MOVES.
+ * If no moves can be found, set *MOVES to NULL. */
+static svn_error_t *
+find_moves_in_natural_history(apr_array_header_t **moves,
+                              const char *repos_relpath,
+                              svn_revnum_t peg_rev,
+                              svn_node_kind_t node_kind,
+                              svn_revnum_t end_rev,
+                              const char *victim_abspath,
+                              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)
+{
+  apr_hash_t *moves_table;
+  apr_array_header_t *revs;
+  apr_array_header_t *most_recent_moves = NULL;
+  int i;
+  apr_pool_t *iterpool;
+
+  *moves = NULL;
+
+  SVN_ERR(find_moves_in_revision_range(&moves_table, repos_relpath,
+                                       repos_root_url, repos_uuid,
+                                       victim_abspath, peg_rev, end_rev,
+                                       ctx, scratch_pool, scratch_pool));
+
+  iterpool = svn_pool_create(scratch_pool);
+
+  /* Scan the moves table for applicable moves. */
+  revs = svn_sort__hash(moves_table, compare_items_as_revs, scratch_pool);
+  for (i = revs->nelts - 1; i >= 0; i--)
+    {
+      svn_sort__item_t item = APR_ARRAY_IDX(revs, i, svn_sort__item_t);
+      apr_array_header_t *moves_in_rev = apr_hash_get(moves_table, item.key,
+                                                      sizeof(svn_revnum_t));
+      int j;
+
+      svn_pool_clear(iterpool);
+
+      /* Was repos relpath moved to its location in this revision? */
+      for (j = 0; j < moves_in_rev->nelts; j++)
+        {
+          struct repos_move_info *move;
+          const char *relpath;
+
+          move = APR_ARRAY_IDX(moves_in_rev, j, struct repos_move_info *);
+          relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
+                                              repos_relpath);
+          if (relpath)
+            {
+              /* If the move did not happen in our peg revision, make
+               * sure this move happened on the same line of history. */
+              if (move->rev != peg_rev)
+                {
+                  svn_client__pathrev_t *yca_loc;
+                  svn_error_t *err;
+
+                  err = find_yca(&yca_loc, repos_relpath, peg_rev,
+                                 repos_relpath, move->rev,
+                                 repos_root_url, repos_uuid,
+                                 NULL, ctx, iterpool, iterpool);
+                  if (err)
+                    {
+                      if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+                        {
+                          svn_error_clear(err);
+                          yca_loc = NULL;
+                        }
+                      else
+                        return svn_error_trace(err);
+                    }
+
+                  if (yca_loc == NULL || yca_loc->rev != move->rev)
+                    continue;
+                }
+
+              if (most_recent_moves == NULL)
+                most_recent_moves =
+                  apr_array_make(result_pool, 1,
+                                 sizeof(struct repos_move_info *));
+
+              /* Copy the move to result pool (even if relpath is ""). */
+              move = new_path_adjusted_move(move, relpath, node_kind,
+                                            result_pool);
+              APR_ARRAY_PUSH(most_recent_moves,
+                             struct repos_move_info *) = move;
+            }
+        }
+
+      /* If we found one move, or several ambiguous moves, we're done. */
+      if (most_recent_moves)
+        break;
+    }
+
+  if (most_recent_moves && most_recent_moves->nelts > 0)
+    {
+      *moves = apr_array_make(result_pool, 1,
+                              sizeof(struct repos_move_info *));
+
+      /* Figure out what happened to the most recent moves in prior
+       * revisions and build move chains. */
+      for (i = 0; i < most_recent_moves->nelts; i++)
+        {
+          struct repos_move_info *move;
+
+          svn_pool_clear(iterpool);
+
+          move = APR_ARRAY_IDX(most_recent_moves, i, struct repos_move_info *);
+          SVN_ERR(trace_moved_node_backwards(moves_table, move,
+                                             ra_session, repos_root_url,
+                                             result_pool, iterpool));
+          /* Follow the move chain backwards. */
+          while (move->prev)
+            move = move->prev;
+
+          /* Return move heads. */
+          APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move;
+        }
+    }
+
+  svn_pool_destroy(iterpool);
+
+  return SVN_NO_ERROR;
+}
+
 /* Implements tree_conflict_get_details_func_t. */
 static svn_error_t *
 conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
@@ -2080,9 +2618,12 @@ conflict_tree_get_details_local_missing(
   svn_node_kind_t replacing_node_kind;
   const char *deleted_basename;
   struct conflict_tree_local_missing_details *details;
-  apr_array_header_t *moves;
+  apr_array_header_t *moves = NULL;
+  apr_array_header_t *sibling_moves = NULL;
   const char *related_repos_relpath;
   svn_revnum_t related_peg_rev;
+  const char *repos_root_url;
+  const char *repos_uuid;
 
   SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
             &old_repos_relpath, &old_rev, NULL, conflict,
@@ -2096,7 +2637,7 @@ conflict_tree_get_details_local_missing(
   deleted_basename = svn_dirent_basename(conflict->local_abspath,
                                          scratch_pool);
   SVN_ERR(svn_wc__node_get_repos_info(&parent_peg_rev, &parent_repos_relpath,
-                                      NULL, NULL,
+                                      &repos_root_url, &repos_uuid,
                                       ctx->wc_ctx,
                                       svn_dirent_dirname(
                                         conflict->local_abspath,
@@ -2126,16 +2667,72 @@ conflict_tree_get_details_local_missing(
             parent_peg_rev, 0, related_repos_relpath, related_peg_rev,
             ctx, conflict->pool, scratch_pool));
 
+  /* If the victim was not deleted then check if the related path was moved. */
   if (deleted_rev == SVN_INVALID_REVNUM)
-    return SVN_NO_ERROR;
+    {
+      const char *victim_abspath;
+      svn_ra_session_t *ra_session;
+      const char *url, *corrected_url;
+      svn_client__pathrev_t *yca_loc;
+      svn_revnum_t end_rev;
+      svn_node_kind_t related_node_kind;
+
+      /* ### The following describes all moves in terms of forward-merges,
+       * should do we something else for reverse-merges? */
+
+      victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+      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,
+                                                   url, NULL, NULL,
+                                                   FALSE,
+                                                   FALSE,
+                                                   ctx,
+                                                   scratch_pool,
+                                                   scratch_pool));
+
+      /* Set END_REV to our best guess of the nearest YCA revision. */
+      SVN_ERR(find_nearest_yca(&yca_loc, related_repos_relpath, related_peg_rev,
+                               parent_repos_relpath, parent_peg_rev,
+                               repos_root_url, repos_uuid, ra_session, ctx,
+                               scratch_pool, scratch_pool));
+      if (yca_loc == NULL)
+        return SVN_NO_ERROR;
+      end_rev = yca_loc->rev;
+
+      /* END_REV must be smaller than RELATED_PEG_REV, else the call
+         to find_moves_in_natural_history() below will error out. */
+      if (end_rev >= related_peg_rev)
+        end_rev = related_peg_rev > 0 ? related_peg_rev - 1 : 0;
+
+      SVN_ERR(svn_ra_check_path(ra_session, "", related_peg_rev,
+                                &related_node_kind, scratch_pool));
+      SVN_ERR(find_moves_in_natural_history(&sibling_moves,
+                                            related_repos_relpath,
+                                            related_peg_rev,
+                                            related_node_kind,
+                                            end_rev,
+                                            victim_abspath,
+                                            repos_root_url, repos_uuid,
+                                            ra_session, ctx,
+                                            conflict->pool, scratch_pool));
+
+      if (sibling_moves == NULL)
+        return SVN_NO_ERROR;
+
+      /* ## TODO: Find the missing node in the WC. */
+    }
 
   details = apr_pcalloc(conflict->pool, sizeof(*details));
   details->deleted_rev = deleted_rev;
   details->deleted_rev_author = deleted_rev_author;
-  details->deleted_repos_relpath = svn_relpath_join(parent_repos_relpath,
-                                                    deleted_basename,
-                                                    conflict->pool); 
+  if (deleted_rev != SVN_INVALID_REVNUM)
+    details->deleted_repos_relpath = svn_relpath_join(parent_repos_relpath,
+                                                      deleted_basename,
+                                                      conflict->pool); 
   details->moves = moves;
+  details->sibling_moves = sibling_moves;
                                          
   conflict->tree_conflict_local_details = details;
 
@@ -2287,22 +2884,74 @@ conflict_tree_get_description_local_miss
                              description, conflict, ctx,
                              result_pool, scratch_pool));
 
-  if (details->moves)
+  if (details->moves || details->sibling_moves)
     {
       struct repos_move_info *move;
+      
+      *description = _("No such file or directory was found in the "
+                       "merge target working copy.\n");
 
-      move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
-      *description = apr_psprintf(
-                       result_pool,
-                       _("No such file or directory was found in the "
-                         "merge target working copy.\nThe item was "
-                         "moved away to '^/%s' in r%ld by %s."),
-                       move->moved_to_repos_relpath,
-                       move->rev, move->rev_author);
-      *description = append_moved_to_chain_description(*description,
-                                                       move->next,
-                                                       result_pool,
-                                                       scratch_pool);
+      if (details->moves)
+        {
+          move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
+          if (move->node_kind == svn_node_file)
+            *description = apr_psprintf(
+                             result_pool,
+                             _("%sThe file was moved to '^/%s' in r%ld by %s."),
+                             *description, move->moved_to_repos_relpath,
+                             move->rev, move->rev_author);
+          else if (move->node_kind == svn_node_dir)
+            *description = apr_psprintf(
+                             result_pool,
+                             _("%sThe directory was moved to '^/%s' in "
+                               "r%ld by %s."),
+                             *description, move->moved_to_repos_relpath,
+                             move->rev, move->rev_author);
+          else
+            *description = apr_psprintf(
+                             result_pool,
+                             _("%sThe item was moved to '^/%s' in r%ld by %s."),
+                             *description, move->moved_to_repos_relpath,
+                             move->rev, move->rev_author);
+          *description = append_moved_to_chain_description(*description,
+                                                           move->next,
+                                                           result_pool,
+                                                           scratch_pool);
+        }
+
+      if (details->sibling_moves)
+        {
+          move = APR_ARRAY_IDX(details->sibling_moves, 0,
+                               struct repos_move_info *);
+          if (move->node_kind == svn_node_file)
+            *description = apr_psprintf(
+                             result_pool,
+                             _("%sThe file '^/%s' was moved to '^/%s' "
+                               "in r%ld by %s."),
+                             *description, move->moved_from_repos_relpath,
+                             move->moved_to_repos_relpath,
+                             move->rev, move->rev_author);
+          else if (move->node_kind == svn_node_dir)
+            *description = apr_psprintf(
+                             result_pool,
+                             _("%sThe directory '^/%s' was moved to '^/%s' "
+                               "in r%ld by %s."),
+                             *description, move->moved_from_repos_relpath,
+                             move->moved_to_repos_relpath,
+                             move->rev, move->rev_author);
+          else
+            *description = apr_psprintf(
+                             result_pool,
+                             _("%sThe item '^/%s' was moved to '^/%s' "
+                               "in r%ld by %s."),
+                             *description, move->moved_from_repos_relpath,
+                             move->moved_to_repos_relpath,
+                             move->rev, move->rev_author);
+          *description = append_moved_to_chain_description(*description,
+                                                           move->next,
+                                                           result_pool,
+                                                           scratch_pool);
+        }
     }
   else
     *description = apr_psprintf(
@@ -3994,7 +4643,8 @@ get_incoming_delete_details_for_reverse_
 }
 
 /* Follow each move chain starting a MOVE all the way to the end to find
- * the possible working copy locations for VICTIM_ABSPATH at PEG_REVISION.
+ * the possible working copy locations for VICTIM_ABSPATH which corresponds
+ * to VICTIM_REPOS_REPLATH@VICTIM_REVISION.
  * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the
  * repos_relpath which is the corresponding move destination in the repository.
  * This function is recursive. */
@@ -4004,7 +4654,8 @@ follow_move_chains(apr_hash_t *wc_move_t
                    svn_client_ctx_t *ctx,
                    const char *victim_abspath,
                    svn_node_kind_t victim_node_kind,
-                   svn_revnum_t peg_revision,
+                   const char *victim_repos_relpath,
+                   svn_revnum_t victim_revision,
                    apr_pool_t *result_pool,
                    apr_pool_t *scratch_pool)
 {
@@ -4012,17 +4663,85 @@ follow_move_chains(apr_hash_t *wc_move_t
    * the working copy and add them to our collection if found. */
   if (move->next == NULL)
     {
-      apr_array_header_t *moved_to_abspaths;
+      apr_array_header_t *candidate_abspaths;
 
-      /* Gather nodes which represent this moved_to_repos_relpath. */
+      /* Gather candidate nodes which represent this moved_to_repos_relpath. */
       SVN_ERR(svn_wc__guess_incoming_move_target_nodes(
-                &moved_to_abspaths, ctx->wc_ctx,
+                &candidate_abspaths, ctx->wc_ctx,
                 victim_abspath, victim_node_kind,
                 move->moved_to_repos_relpath,
-                peg_revision, result_pool, scratch_pool));
-      if (moved_to_abspaths->nelts > 0)
-        svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath,
-                      moved_to_abspaths);
+                scratch_pool, scratch_pool));
+      if (candidate_abspaths->nelts > 0)
+        {
+          apr_array_header_t *moved_to_abspaths;
+          int i;
+          apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+          moved_to_abspaths = apr_array_make(result_pool, 1,
+                                             sizeof (const char *));
+
+          for (i = 0; i < candidate_abspaths->nelts; i++)
+            {
+              const char *candidate_abspath;
+              const char *repos_root_url;
+              const char *repos_uuid;
+              const char *candidate_repos_relpath;
+              svn_revnum_t candidate_revision;
+
+              svn_pool_clear(iterpool);
+
+              candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i,
+                                                const char *);
+              SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision,
+                                              &candidate_repos_relpath,
+                                              &repos_root_url,
+                                              &repos_uuid,
+                                              NULL, NULL,
+                                              ctx->wc_ctx,
+                                              candidate_abspath,
+                                              FALSE,
+                                              iterpool, iterpool));
+
+              if (candidate_revision == SVN_INVALID_REVNUM)
+                continue;
+
+              /* If the conflict victim and the move target candidate
+               * are not from the same revision we must ensure that
+               * they are related. */
+               if (candidate_revision != victim_revision)
+                {
+                  svn_client__pathrev_t *yca_loc;
+                  svn_error_t *err;
+
+                  err = find_yca(&yca_loc, victim_repos_relpath,
+                                 victim_revision,
+                                 candidate_repos_relpath,
+                                 candidate_revision,
+                                 repos_root_url, repos_uuid,
+                                 NULL, ctx, iterpool, iterpool);
+                  if (err)
+                    {
+                      if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+                        {
+                          svn_error_clear(err);
+                          yca_loc = NULL;
+                        }
+                      else
+                        return svn_error_trace(err);
+                    }
+
+                  if (yca_loc == NULL)
+                    continue;
+                }
+
+              APR_ARRAY_PUSH(moved_to_abspaths, const char *) =
+                apr_pstrdup(result_pool, candidate_abspath);
+            }
+          svn_pool_destroy(iterpool);
+
+          svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath,
+                        moved_to_abspaths);
+        }
     }
   else
     {
@@ -4040,7 +4759,8 @@ follow_move_chains(apr_hash_t *wc_move_t
           next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *);
           SVN_ERR(follow_move_chains(wc_move_targets, next_move,
                                      ctx, victim_abspath, victim_node_kind,
-                                     peg_revision, result_pool, iterpool));
+                                     victim_repos_relpath, victim_revision,
+                                     result_pool, iterpool));
                                         
         }
       svn_pool_destroy(iterpool);
@@ -4058,14 +4778,17 @@ init_wc_move_targets(struct conflict_tre
   int i;
   const char *victim_abspath;
   svn_node_kind_t victim_node_kind;
+  const char *incoming_new_repos_relpath;
   svn_revnum_t incoming_new_pegrev;
   svn_wc_operation_t operation;
 
   victim_abspath = svn_client_conflict_get_local_abspath(conflict);
   victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
   operation = svn_client_conflict_get_operation(conflict);
+  /* ### Should we get the old location in case of reverse-merges? */
   SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
-            NULL, &incoming_new_pegrev, NULL, conflict,
+            &incoming_new_repos_relpath, &incoming_new_pegrev,
+            NULL, conflict,
             scratch_pool, scratch_pool));
   details->wc_move_targets = apr_hash_make(conflict->pool);
   for (i = 0; i < details->moves->nelts; i++)
@@ -4076,6 +4799,7 @@ init_wc_move_targets(struct conflict_tre
       SVN_ERR(follow_move_chains(details->wc_move_targets, move,
                                  ctx, victim_abspath,
                                  victim_node_kind,
+                                 incoming_new_repos_relpath,
                                  incoming_new_pegrev,
                                  conflict->pool, scratch_pool));
     }
@@ -9264,6 +9988,7 @@ configure_option_local_move_file_merge(s
               SVN_ERR(follow_move_chains(wc_move_targets, move, ctx,
                                          conflict->local_abspath,
                                          svn_node_file,
+                                         incoming_new_repos_relpath,
                                          incoming_new_pegrev,
                                          scratch_pool, iterpool));
             }
@@ -9603,6 +10328,22 @@ svn_client_conflict_tree_get_resolution_
   return SVN_NO_ERROR;
 }
 
+/* Swallow authz failures and return SVN_NO_ERROR in that case.
+ * Otherwise, return ERR unchanged. */
+static svn_error_t *
+ignore_authz_failures(svn_error_t *err)
+{
+  if (err && (   svn_error_find_cause(err, SVN_ERR_AUTHZ_UNREADABLE)
+              || svn_error_find_cause(err, SVN_ERR_RA_NOT_AUTHORIZED)
+              || svn_error_find_cause(err, SVN_ERR_RA_DAV_FORBIDDEN)))
+    {
+      svn_error_clear(err);
+      err = SVN_NO_ERROR;
+    }
+
+  return err;
+}
+
 svn_error_t *
 svn_client_conflict_tree_get_details(svn_client_conflict_t *conflict,
                                      svn_client_ctx_t *ctx,
@@ -9622,13 +10363,18 @@ svn_client_conflict_tree_get_details(svn
                                   scratch_pool);
     }
 
+  /* Collecting conflict details may fail due to insufficient access rights.
+   * This is not a failure but simply restricts our future options. */
   if (conflict->tree_conflict_get_incoming_details_func)
-    SVN_ERR(conflict->tree_conflict_get_incoming_details_func(conflict, ctx,
-                                                              scratch_pool));
+    SVN_ERR(ignore_authz_failures(
+      conflict->tree_conflict_get_incoming_details_func(conflict, ctx,
+                                                        scratch_pool)));
+
 
   if (conflict->tree_conflict_get_local_details_func)
-    SVN_ERR(conflict->tree_conflict_get_local_details_func(conflict, ctx,
-                                                           scratch_pool));
+    SVN_ERR(ignore_authz_failures(
+      conflict->tree_conflict_get_local_details_func(conflict, ctx,
+                                                    scratch_pool)));
 
   if (ctx->notify_func2)
     {

Modified: subversion/branches/shelve-checkpoint3/subversion/libsvn_client/diff.c
URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint3/subversion/libsvn_client/diff.c?rev=1813858&r1=1813857&r2=1813858&view=diff
==============================================================================
--- subversion/branches/shelve-checkpoint3/subversion/libsvn_client/diff.c (original)
+++ subversion/branches/shelve-checkpoint3/subversion/libsvn_client/diff.c Tue Oct 31 09:36:53 2017
@@ -422,7 +422,8 @@ print_git_diff_header(svn_stream_t *os,
                                             scratch_pool));
       *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
                            rev1, scratch_pool);
-      *label2 = diff_label("/dev/null", rev2, scratch_pool);
+      *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
+                           rev2, scratch_pool);
 
     }
   else if (operation == svn_diff_op_copied)
@@ -447,7 +448,8 @@ print_git_diff_header(svn_stream_t *os,
                                           repos_relpath1, repos_relpath2,
                                           exec_bit2, symlink_bit2,
                                           scratch_pool));
-      *label1 = diff_label("/dev/null", rev1, scratch_pool);
+      *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
+                           rev1, scratch_pool);
       *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
                            rev2, scratch_pool);
     }

Modified: subversion/branches/shelve-checkpoint3/subversion/libsvn_client/list.c
URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint3/subversion/libsvn_client/list.c?rev=1813858&r1=1813857&r2=1813858&view=diff
==============================================================================
--- subversion/branches/shelve-checkpoint3/subversion/libsvn_client/list.c (original)
+++ subversion/branches/shelve-checkpoint3/subversion/libsvn_client/list.c Tue Oct 31 09:36:53 2017
@@ -37,6 +37,7 @@
 #include "private/svn_fspath.h"
 #include "private/svn_ra_private.h"
 #include "private/svn_sorts_private.h"
+#include "private/svn_utf_private.h"
 #include "private/svn_wc_private.h"
 #include "svn_private_config.h"
 
@@ -69,23 +70,16 @@ list_internal(const char *path_or_url,
               apr_pool_t *pool);
 
 /* Return TRUE if S matches any of the const char * in PATTERNS.
- * Note that any S will match if PATTERNS is empty. */
+ * Note that any S will match if PATTERNS is empty.
+ * Use SCRATCH_BUFFER for temporary string contents. */
 static svn_boolean_t
 match_patterns(const char *s,
-               const apr_array_header_t *patterns)
+               const apr_array_header_t *patterns,
+               svn_membuf_t *scratch_buffer)
 {
-  int i;
-  if (!patterns)
-    return TRUE;
-
-  for (i = 0; i < patterns->nelts; ++i)
-    {
-      const char *pattern = APR_ARRAY_IDX(patterns, i, const char *);
-      if (apr_fnmatch(pattern, s, APR_FNM_PERIOD) == APR_SUCCESS)
-        return TRUE;
-    }
-
-  return FALSE;
+  return patterns
+       ? svn_utf__fuzzy_glob_match(s, patterns, scratch_buffer)
+       : TRUE;
 }
 
 /* Get the directory entries of DIR at REV (relative to the root of
@@ -113,6 +107,8 @@ match_patterns(const char *s,
 
    EXTERNAL_PARENT_URL and EXTERNAL_TARGET are set when external items
    are listed, otherwise both are set to NULL by the caller.
+
+   Use SCRATCH_BUFFER for temporary string contents.
 */
 static svn_error_t *
 get_dir_contents(apr_uint32_t dirent_fields,
@@ -129,6 +125,7 @@ get_dir_contents(apr_uint32_t dirent_fie
                  const char *external_target,
                  svn_client_list_func2_t list_func,
                  void *baton,
+                 svn_membuf_t *scratch_buffer,
                  apr_pool_t *result_pool,
                  apr_pool_t *scratch_pool)
 {
@@ -203,7 +200,7 @@ get_dir_contents(apr_uint32_t dirent_fie
       if (the_ent->kind == svn_node_file
           || depth == svn_depth_immediates
           || depth == svn_depth_infinity)
-        if (match_patterns(item->key, patterns))
+        if (match_patterns(item->key, patterns, scratch_buffer))
           SVN_ERR(list_func(baton, path, the_ent, lock, fs_path,
                             external_parent_url, external_target, iterpool));
 
@@ -214,7 +211,7 @@ get_dir_contents(apr_uint32_t dirent_fie
                                  locks, fs_path, patterns, depth, ctx,
                                  externals, external_parent_url,
                                  external_target, list_func, baton,
-                                 result_pool, iterpool));
+                                 scratch_buffer, result_pool, iterpool));
     }
 
   svn_pool_destroy(iterpool);
@@ -329,6 +326,7 @@ list_internal(const char *path_or_url,
   svn_error_t *err;
   apr_hash_t *locks;
   apr_hash_t *externals;
+  svn_membuf_t scratch_buffer;
 
   if (include_externals)
     externals = apr_hash_make(pool);
@@ -391,8 +389,13 @@ list_internal(const char *path_or_url,
                              _("URL '%s' non-existent in revision %ld"),
                              loc->url, loc->rev);
 
+  /* We need a scratch buffer for temporary string data.
+   * Create one with a reasonable initial size. */
+  svn_membuf__create(&scratch_buffer, 256, pool);
+
   /* Report the dirent for the target. */
-  if (match_patterns(svn_dirent_dirname(fs_path, pool), patterns))
+  if (match_patterns(svn_dirent_dirname(fs_path, pool), patterns,
+                     &scratch_buffer))
     SVN_ERR(list_func(baton, "", dirent, locks
                       ? (svn_hash_gets(locks, fs_path))
                       : NULL, fs_path, external_parent_url,
@@ -405,7 +408,7 @@ list_internal(const char *path_or_url,
     SVN_ERR(get_dir_contents(dirent_fields, "", loc->rev, ra_session, locks,
                              fs_path, patterns, depth, ctx, externals,
                              external_parent_url, external_target, list_func,
-                             baton, pool, pool));
+                             baton, &scratch_buffer, pool, pool));
 
   /* We handle externals after listing entries under path_or_url, so that
      handling external items (and any errors therefrom) doesn't delay

Modified: subversion/branches/shelve-checkpoint3/subversion/libsvn_client/shelve.c
URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint3/subversion/libsvn_client/shelve.c?rev=1813858&r1=1813857&r2=1813858&view=diff
==============================================================================
--- subversion/branches/shelve-checkpoint3/subversion/libsvn_client/shelve.c (original)
+++ subversion/branches/shelve-checkpoint3/subversion/libsvn_client/shelve.c Tue Oct 31 09:36:53 2017
@@ -42,12 +42,12 @@
 
 /*  */
 static svn_error_t *
-validate_shelf_name(const char *shelf_name,
-                    apr_pool_t *scratch_pool)
+validate_name(const char *name,
+              apr_pool_t *scratch_pool)
 {
-  if (shelf_name[0] == '\0' || strchr(shelf_name, '/'))
+  if (name[0] == '\0' || strchr(name, '/'))
     return svn_error_createf(SVN_ERR_BAD_CHANGELIST_NAME, NULL,
-                             _("Shelve: Bad name '%s'"), shelf_name);
+                             _("Shelve: Bad name '%s'"), name);
 
   return SVN_NO_ERROR;
 }
@@ -55,7 +55,7 @@ validate_shelf_name(const char *shelf_na
 /*  */
 static svn_error_t *
 get_patch_abspath(char **patch_abspath,
-                  const char *shelf_name,
+                  const char *name,
                   const char *wc_root_abspath,
                   svn_client_ctx_t *ctx,
                   apr_pool_t *result_pool,
@@ -66,13 +66,13 @@ get_patch_abspath(char **patch_abspath,
 
   SVN_ERR(svn_wc__get_shelves_dir(&dir, ctx->wc_ctx, wc_root_abspath,
                                   scratch_pool, scratch_pool));
-  filename = apr_pstrcat(scratch_pool, shelf_name, ".patch", SVN_VA_NULL);
+  filename = apr_pstrcat(scratch_pool, name, ".patch", SVN_VA_NULL);
   *patch_abspath = svn_dirent_join(dir, filename, result_pool);
   return SVN_NO_ERROR;
 }
 
 svn_error_t *
-svn_client_shelf_write_patch(const char *shelf_name,
+svn_client_shelf_write_patch(const char *name,
                              const char *message,
                              const char *wc_root_abspath,
                              svn_boolean_t overwrite_existing,
@@ -93,7 +93,9 @@ svn_client_shelf_write_patch(const char
   svn_opt_revision_t start_revision = {svn_opt_revision_base, {0}};
   svn_opt_revision_t end_revision = {svn_opt_revision_working, {0}};
 
-  SVN_ERR(get_patch_abspath(&patch_abspath, shelf_name, wc_root_abspath,
+  printf("writing '%s.patch'\n", name);
+
+  SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
                             ctx, scratch_pool, scratch_pool));
 
   /* Get streams for the output and any error output of the diff. */
@@ -156,7 +158,7 @@ svn_client_shelf_write_patch(const char
 }
 
 svn_error_t *
-svn_client_shelf_apply_patch(const char *shelf_name,
+svn_client_shelf_apply_patch(const char *name,
                              const char *wc_root_abspath,
                              svn_boolean_t reverse,
                              svn_boolean_t dry_run,
@@ -165,7 +167,7 @@ svn_client_shelf_apply_patch(const char
 {
   char *patch_abspath;
 
-  SVN_ERR(get_patch_abspath(&patch_abspath, shelf_name, wc_root_abspath,
+  SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
                             ctx, scratch_pool, scratch_pool));
   SVN_ERR(svn_client_patch(patch_abspath, wc_root_abspath,
                            dry_run, 0 /*strip*/,
@@ -178,25 +180,34 @@ svn_client_shelf_apply_patch(const char
 }
 
 svn_error_t *
-svn_client_shelf_delete_patch(const char *shelf_name,
+svn_client_shelf_delete_patch(const char *name,
                               const char *wc_root_abspath,
                               svn_client_ctx_t *ctx,
                               apr_pool_t *scratch_pool)
 {
-  char *patch_abspath;
+  char *patch_abspath, *to_abspath;
 
-  SVN_ERR(get_patch_abspath(&patch_abspath, shelf_name, wc_root_abspath,
+  SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
                             ctx, scratch_pool, scratch_pool));
-  SVN_ERR(svn_io_remove_file2(patch_abspath, FALSE /*ignore_enoent*/,
+  to_abspath = apr_pstrcat(scratch_pool, patch_abspath, ".bak", SVN_VA_NULL);
+
+  /* remove any previous backup */
+  SVN_ERR(svn_io_remove_file2(to_abspath, TRUE /*ignore_enoent*/,
+                              scratch_pool));
+
+  /* move the patch to a backup file */
+  printf("moving '%s.patch' to '%s.patch.bak'\n", name, name);
+  SVN_ERR(svn_io_file_rename2(patch_abspath, to_abspath, FALSE /*flush_to_disk*/,
                               scratch_pool));
   return SVN_NO_ERROR;
 }
 
 svn_error_t *
-svn_client_shelve(const char *shelf_name,
+svn_client_shelve(const char *name,
                   const apr_array_header_t *paths,
                   svn_depth_t depth,
                   const apr_array_header_t *changelists,
+                  svn_boolean_t keep_local,
                   svn_boolean_t dry_run,
                   svn_client_ctx_t *ctx,
                   apr_pool_t *pool)
@@ -206,7 +217,7 @@ svn_client_shelve(const char *shelf_name
   const char *message = "";
   svn_error_t *err;
 
-  SVN_ERR(validate_shelf_name(shelf_name, pool));
+  SVN_ERR(validate_name(name, pool));
 
   /* ### TODO: check all paths are in same WC; for now use first path */
   SVN_ERR(svn_dirent_get_absolute(&local_abspath,
@@ -226,7 +237,7 @@ svn_client_shelve(const char *shelf_name
         return SVN_NO_ERROR;
     }
 
-  err = svn_client_shelf_write_patch(shelf_name, message, wc_root_abspath,
+  err = svn_client_shelf_write_patch(name, message, wc_root_abspath,
                                      FALSE /*overwrite_existing*/,
                                      paths, depth, changelists,
                                      ctx, pool);
@@ -234,20 +245,23 @@ svn_client_shelve(const char *shelf_name
     {
       return svn_error_quick_wrapf(err,
                                    "Shelved change '%s' already exists",
-                                   shelf_name);
+                                   name);
     }
   else
     SVN_ERR(err);
 
-  /* Reverse-apply the patch. This should be a safer way to remove those
-     changes from the WC than running a 'revert' operation. */
-  SVN_ERR(svn_client_shelf_apply_patch(shelf_name, wc_root_abspath,
-                                       TRUE /*reverse*/, dry_run,
-                                       ctx, pool));
+  if (!keep_local)
+    {
+      /* Reverse-apply the patch. This should be a safer way to remove those
+         changes from the WC than running a 'revert' operation. */
+      SVN_ERR(svn_client_shelf_apply_patch(name, wc_root_abspath,
+                                           TRUE /*reverse*/, dry_run,
+                                           ctx, pool));
+    }
 
   if (dry_run)
     {
-      SVN_ERR(svn_client_shelf_delete_patch(shelf_name, wc_root_abspath,
+      SVN_ERR(svn_client_shelf_delete_patch(name, wc_root_abspath,
                                             ctx, pool));
     }
 
@@ -255,7 +269,7 @@ svn_client_shelve(const char *shelf_name
 }
 
 svn_error_t *
-svn_client_unshelve(const char *shelf_name,
+svn_client_unshelve(const char *name,
                     const char *local_abspath,
                     svn_boolean_t keep,
                     svn_boolean_t dry_run,
@@ -265,20 +279,20 @@ svn_client_unshelve(const char *shelf_na
   const char *wc_root_abspath;
   svn_error_t *err;
 
-  SVN_ERR(validate_shelf_name(shelf_name, pool));
+  SVN_ERR(validate_name(name, pool));
 
   SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
                                  local_abspath, ctx, pool, pool));
 
   /* Apply the patch. */
-  err = svn_client_shelf_apply_patch(shelf_name, wc_root_abspath,
+  err = svn_client_shelf_apply_patch(name, wc_root_abspath,
                                      FALSE /*reverse*/, dry_run,
                                      ctx, pool);
   if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
     {
       return svn_error_quick_wrapf(err,
                                    "Shelved change '%s' not found",
-                                   shelf_name);
+                                   name);
     }
   else
     SVN_ERR(err);
@@ -286,7 +300,7 @@ svn_client_unshelve(const char *shelf_na
   /* Remove the patch. */
   if (! keep && ! dry_run)
     {
-      SVN_ERR(svn_client_shelf_delete_patch(shelf_name, wc_root_abspath,
+      SVN_ERR(svn_client_shelf_delete_patch(name, wc_root_abspath,
                                             ctx, pool));
     }
 
@@ -294,7 +308,7 @@ svn_client_unshelve(const char *shelf_na
 }
 
 svn_error_t *
-svn_client_shelves_delete(const char *shelf_name,
+svn_client_shelves_delete(const char *name,
                           const char *local_abspath,
                           svn_boolean_t dry_run,
                           svn_client_ctx_t *ctx,
@@ -302,7 +316,7 @@ svn_client_shelves_delete(const char *sh
 {
   const char *wc_root_abspath;
 
-  SVN_ERR(validate_shelf_name(shelf_name, pool));
+  SVN_ERR(validate_name(name, pool));
 
   SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
                                  local_abspath, ctx, pool, pool));
@@ -312,13 +326,13 @@ svn_client_shelves_delete(const char *sh
     {
       svn_error_t *err;
 
-      err = svn_client_shelf_delete_patch(shelf_name, wc_root_abspath,
+      err = svn_client_shelf_delete_patch(name, wc_root_abspath,
                                           ctx, pool);
       if (err && APR_STATUS_IS_ENOENT(err->apr_err))
         {
           return svn_error_quick_wrapf(err,
                                        "Shelved change '%s' not found",
-                                       shelf_name);
+                                       name);
         }
       else
         SVN_ERR(err);
@@ -327,32 +341,82 @@ svn_client_shelves_delete(const char *sh
   return SVN_NO_ERROR;
 }
 
+/* ### Currently just reads the first line.
+ */
+static svn_error_t *
+read_logmsg_from_patch(const char **logmsg,
+                       const char *patch_abspath,
+                       apr_pool_t *result_pool,
+                       apr_pool_t *scratch_pool)
+{
+  apr_file_t *file;
+  svn_stream_t *stream;
+  svn_boolean_t eof;
+  svn_stringbuf_t *line;
+
+  SVN_ERR(svn_io_file_open(&file, patch_abspath,
+                           APR_FOPEN_READ, APR_FPROT_OS_DEFAULT, scratch_pool));
+  stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool);
+  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool));
+  SVN_ERR(svn_stream_close(stream));
+  *logmsg = line->data;
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
-svn_client_shelves_list(apr_hash_t **dirents,
+svn_client_shelves_list(apr_hash_t **shelved_patch_infos,
                         const char *local_abspath,
                         svn_client_ctx_t *ctx,
                         apr_pool_t *result_pool,
                         apr_pool_t *scratch_pool)
 {
   char *shelves_dir;
+  apr_hash_t *dirents;
   apr_hash_index_t *hi;
 
   SVN_ERR(svn_wc__get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath,
                                   scratch_pool, scratch_pool));
-  SVN_ERR(svn_io_get_dirents3(dirents, shelves_dir, FALSE /*only_check_type*/,
+  SVN_ERR(svn_io_get_dirents3(&dirents, shelves_dir, FALSE /*only_check_type*/,
                               result_pool, scratch_pool));
 
+  *shelved_patch_infos = apr_hash_make(result_pool);
+
   /* Remove non-shelves */
-  for (hi = apr_hash_first(scratch_pool, *dirents); hi; hi = apr_hash_next(hi))
+  for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
     {
-      const char *name = apr_hash_this_key(hi);
+      const char *filename = apr_hash_this_key(hi);
+      int len = strlen(filename);
 
-      if (! strstr(name, ".patch"))
+      if (len > 6 && strcmp(filename + len - 6, ".patch") == 0)
         {
-          svn_hash_sets(*dirents, name, NULL);
+          const char *name = apr_pstrndup(result_pool, filename, len - 6);
+          svn_client_shelved_patch_info_t *info
+            = apr_palloc(result_pool, sizeof(*info));
+
+          info->dirent = apr_hash_this_val(hi);
+          info->mtime = info->dirent->mtime;
+          info->patch_path
+            = svn_dirent_join(shelves_dir, filename, result_pool);
+          SVN_ERR(read_logmsg_from_patch(&info->message, info->patch_path,
+                                         result_pool, scratch_pool));
+
+          svn_hash_sets(*shelved_patch_infos, name, info);
         }
     }
 
   return SVN_NO_ERROR;
 }
 
+svn_error_t *
+svn_client_shelves_any(svn_boolean_t *any_shelved,
+                       const char *local_abspath,
+                       svn_client_ctx_t *ctx,
+                       apr_pool_t *scratch_pool)
+{
+  apr_hash_t *shelved_patch_infos;
+
+  SVN_ERR(svn_client_shelves_list(&shelved_patch_infos, local_abspath,
+                                  ctx, scratch_pool, scratch_pool));
+  *any_shelved = apr_hash_count(shelved_patch_infos) != 0;
+  return SVN_NO_ERROR;
+}

Modified: subversion/branches/shelve-checkpoint3/subversion/libsvn_ra_serf/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint3/subversion/libsvn_ra_serf/merge.c?rev=1813858&r1=1813857&r2=1813858&view=diff
==============================================================================
--- subversion/branches/shelve-checkpoint3/subversion/libsvn_ra_serf/merge.c (original)
+++ subversion/branches/shelve-checkpoint3/subversion/libsvn_ra_serf/merge.c Tue Oct 31 09:36:53 2017
@@ -79,6 +79,7 @@ typedef struct merge_context_t
 
   apr_hash_t *lock_tokens;
   svn_boolean_t keep_locks;
+  svn_boolean_t disable_merge_response;
 
   const char *merge_resource_url; /* URL of resource to be merged. */
   const char *merge_url; /* URL at which the MERGE request is aimed. */
@@ -275,12 +276,17 @@ setup_merge_headers(serf_bucket_t *heade
                     apr_pool_t *scratch_pool)
 {
   merge_context_t *ctx = baton;
+  apr_array_header_t *vals = apr_array_make(scratch_pool, 2,
+                                            sizeof(const char *));
 
   if (!ctx->keep_locks)
-    {
-      serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
-                              SVN_DAV_OPTION_RELEASE_LOCKS);
-    }
+    APR_ARRAY_PUSH(vals, const char *) = SVN_DAV_OPTION_RELEASE_LOCKS;
+  if (ctx->disable_merge_response)
+    APR_ARRAY_PUSH(vals, const char *) = SVN_DAV_OPTION_NO_MERGE_RESPONSE;
+
+  if (vals->nelts > 0)
+    serf_bucket_headers_set(headers, SVN_DAV_OPTIONS_HEADER,
+                            svn_cstring_join2(vals, " ", FALSE, scratch_pool));
 
   return SVN_NO_ERROR;
 }
@@ -412,6 +418,13 @@ svn_ra_serf__run_merge(const svn_commit_
   merge_ctx->lock_tokens = lock_tokens;
   merge_ctx->keep_locks = keep_locks;
 
+  /* We don't need the full merge response when working over HTTPv2.
+   * Over HTTPv1, this response is only required with a non-null
+   * svn_ra_push_wc_prop_func_t callback. */
+  merge_ctx->disable_merge_response =
+    SVN_RA_SERF__HAVE_HTTPV2_SUPPORT(session) ||
+    session->wc_callbacks->push_wc_prop == NULL;
+
   merge_ctx->commit_info = svn_create_commit_info(result_pool);
 
   merge_ctx->merge_url = session->session_url.path;

Modified: subversion/branches/shelve-checkpoint3/subversion/libsvn_repos/deprecated.c
URL: http://svn.apache.org/viewvc/subversion/branches/shelve-checkpoint3/subversion/libsvn_repos/deprecated.c?rev=1813858&r1=1813857&r2=1813858&view=diff
==============================================================================
--- subversion/branches/shelve-checkpoint3/subversion/libsvn_repos/deprecated.c (original)
+++ subversion/branches/shelve-checkpoint3/subversion/libsvn_repos/deprecated.c Tue Oct 31 09:36:53 2017
@@ -822,6 +822,7 @@ svn_repos_dump_fs3(svn_repos_t *repos,
                                             TRUE,
                                             notify_func,
                                             notify_baton,
+                                            NULL, NULL,
                                             cancel_func,
                                             cancel_baton,
                                             pool));
@@ -901,6 +902,31 @@ svn_repos_verify_fs(svn_repos_t *repos,
 /*** From load.c ***/
 
 svn_error_t *
+svn_repos_load_fs5(svn_repos_t *repos,
+                   svn_stream_t *dumpstream,
+                   svn_revnum_t start_rev,
+                   svn_revnum_t end_rev,
+                   enum svn_repos_load_uuid uuid_action,
+                   const char *parent_dir,
+                   svn_boolean_t use_pre_commit_hook,
+                   svn_boolean_t use_post_commit_hook,
+                   svn_boolean_t validate_props,
+                   svn_boolean_t ignore_dates,
+                   svn_repos_notify_func_t notify_func,
+                   void *notify_baton,
+                   svn_cancel_func_t cancel_func,
+                   void *cancel_baton,
+                   apr_pool_t *pool)
+{
+  return svn_repos_load_fs6(repos, dumpstream, start_rev, end_rev,
+                            uuid_action, parent_dir,
+                            use_post_commit_hook, use_post_commit_hook,
+                            validate_props, ignore_dates, FALSE,
+                            notify_func, notify_baton,
+                            cancel_func, cancel_baton, pool);
+}
+
+svn_error_t *
 svn_repos_load_fs4(svn_repos_t *repos,
                    svn_stream_t *dumpstream,
                    svn_revnum_t start_rev,
@@ -1092,6 +1118,40 @@ svn_repos_load_fs(svn_repos_t *repos,
 }
 
 svn_error_t *
+svn_repos_get_fs_build_parser5(const svn_repos_parse_fns3_t **parser,
+                               void **parse_baton,
+                               svn_repos_t *repos,
+                               svn_revnum_t start_rev,
+                               svn_revnum_t end_rev,
+                               svn_boolean_t use_history,
+                               svn_boolean_t validate_props,
+                               enum svn_repos_load_uuid uuid_action,
+                               const char *parent_dir,
+                               svn_boolean_t use_pre_commit_hook,
+                               svn_boolean_t use_post_commit_hook,
+                               svn_boolean_t ignore_dates,
+                               svn_repos_notify_func_t notify_func,
+                               void *notify_baton,
+                               apr_pool_t *pool)
+{
+  SVN_ERR(svn_repos_get_fs_build_parser6(parser, parse_baton,
+                                         repos,
+                                         start_rev, end_rev,
+                                         use_history,
+                                         validate_props,
+                                         uuid_action,
+                                         parent_dir,
+                                         use_pre_commit_hook,
+                                         use_post_commit_hook,
+                                         ignore_dates,
+                                         FALSE /* normalize_props */,
+                                         notify_func,
+                                         notify_baton,
+                                         pool));
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
 svn_repos_get_fs_build_parser4(const svn_repos_parse_fns3_t **callbacks,
                                void **parse_baton,
                                svn_repos_t *repos,



Mime
View raw message