subversion-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From s...@apache.org
Subject svn commit: r1813173 [2/6] - in /subversion/branches/addremove: ./ build/generator/ subversion/bindings/javahl/native/ subversion/bindings/swig/include/ subversion/include/ subversion/include/private/ subversion/libsvn_client/ subversion/libsvn_ra_serf...
Date Tue, 24 Oct 2017 12:40:48 GMT
Modified: subversion/branches/addremove/subversion/libsvn_client/conflicts.c
URL: http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_client/conflicts.c?rev=1813173&r1=1813172&r2=1813173&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_client/conflicts.c (original)
+++ subversion/branches/addremove/subversion/libsvn_client/conflicts.c Tue Oct 24 12:40:47 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/addremove/subversion/libsvn_client/diff.c
URL: http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_client/diff.c?rev=1813173&r1=1813172&r2=1813173&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_client/diff.c (original)
+++ subversion/branches/addremove/subversion/libsvn_client/diff.c Tue Oct 24 12:40:47 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/addremove/subversion/libsvn_client/list.c
URL: http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_client/list.c?rev=1813173&r1=1813172&r2=1813173&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_client/list.c (original)
+++ subversion/branches/addremove/subversion/libsvn_client/list.c Tue Oct 24 12:40:47 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/addremove/subversion/libsvn_ra_serf/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_ra_serf/merge.c?rev=1813173&r1=1813172&r2=1813173&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_ra_serf/merge.c (original)
+++ subversion/branches/addremove/subversion/libsvn_ra_serf/merge.c Tue Oct 24 12:40:47 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/addremove/subversion/libsvn_repos/deprecated.c
URL: http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_repos/deprecated.c?rev=1813173&r1=1813172&r2=1813173&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_repos/deprecated.c (original)
+++ subversion/branches/addremove/subversion/libsvn_repos/deprecated.c Tue Oct 24 12:40:47 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,

Modified: subversion/branches/addremove/subversion/libsvn_repos/dump.c
URL: http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_repos/dump.c?rev=1813173&r1=1813172&r2=1813173&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_repos/dump.c (original)
+++ subversion/branches/addremove/subversion/libsvn_repos/dump.c Tue Oct 24 12:40:47 2017
@@ -1922,14 +1922,17 @@ get_dump_editor(const svn_delta_editor_t
 
 /* Helper for svn_repos_dump_fs.
 
-   Write a revision record of REV in FS to writable STREAM, using POOL.
+   Write a revision record of REV in REPOS to writable STREAM, using POOL.
    Dump revision properties as well if INCLUDE_REVPROPS has been set.
+   AUTHZ_FUNC and AUTHZ_BATON are passed directly to the repos layer.
  */
 static svn_error_t *
 write_revision_record(svn_stream_t *stream,
-                      svn_fs_t *fs,
+                      svn_repos_t *repos,
                       svn_revnum_t rev,
                       svn_boolean_t include_revprops,
+                      svn_repos_authz_func_t authz_func,
+                      void *authz_baton,
                       apr_pool_t *pool)
 {
   apr_hash_t *props;
@@ -1938,7 +1941,8 @@ write_revision_record(svn_stream_t *stre
 
   if (include_revprops)
     {
-      SVN_ERR(svn_fs_revision_proplist2(&props, fs, rev, FALSE, pool, pool));
+      SVN_ERR(svn_repos_fs_revision_proplist(&props, repos, rev,
+                                             authz_func, authz_baton, pool));
 
       /* Run revision date properties through the time conversion to
         canonicalize them. */
@@ -1965,6 +1969,27 @@ write_revision_record(svn_stream_t *stre
   return SVN_NO_ERROR;
 }
 
+/* Baton for dump_filter_authz_func(). */
+typedef struct dump_filter_baton_t
+{
+  svn_repos_dump_filter_func_t filter_func;
+  void *filter_baton;
+} dump_filter_baton_t;
+
+/* Implements svn_repos_authz_func_t. */
+static svn_error_t *
+dump_filter_authz_func(svn_boolean_t *allowed,
+                       svn_fs_root_t *root,
+                       const char *path,
+                       void *baton,
+                       apr_pool_t *pool)
+{
+  dump_filter_baton_t *b = baton;
+
+  return svn_error_trace(b->filter_func(allowed, root, path, b->filter_baton,
+                                        pool));
+}
+
 
 
 /* The main dumper. */
@@ -1979,6 +2004,8 @@ svn_repos_dump_fs4(svn_repos_t *repos,
                    svn_boolean_t include_changes,
                    svn_repos_notify_func_t notify_func,
                    void *notify_baton,
+                   svn_repos_dump_filter_func_t filter_func,
+                   void *filter_baton,
                    svn_cancel_func_t cancel_func,
                    void *cancel_baton,
                    apr_pool_t *pool)
@@ -1994,6 +2021,8 @@ svn_repos_dump_fs4(svn_repos_t *repos,
   svn_boolean_t found_old_reference = FALSE;
   svn_boolean_t found_old_mergeinfo = FALSE;
   svn_repos_notify_t *notify;
+  svn_repos_authz_func_t authz_func;
+  dump_filter_baton_t authz_baton = {0};
 
   /* Make sure we catch up on the latest revprop changes.  This is the only
    * time we will refresh the revprop data in this query. */
@@ -2022,6 +2051,20 @@ svn_repos_dump_fs4(svn_repos_t *repos,
                                "(youngest revision is %ld)"),
                              end_rev, youngest);
 
+  /* We use read authz callback to implement dump filtering. If there is no
+   * read access for some node, it will be excluded from dump as well as
+   * references to it (e.g. copy source). */
+  if (filter_func)
+    {
+      authz_func = dump_filter_authz_func;
+      authz_baton.filter_func = filter_func;
+      authz_baton.filter_baton = filter_baton;
+    }
+  else
+    {
+      authz_func = NULL;
+    }
+
   /* Write out the UUID. */
   SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
 
@@ -2057,8 +2100,8 @@ svn_repos_dump_fs4(svn_repos_t *repos,
         SVN_ERR(cancel_func(cancel_baton));
 
       /* Write the revision record. */
-      SVN_ERR(write_revision_record(stream, fs, rev, include_revprops,
-                                    iterpool));
+      SVN_ERR(write_revision_record(stream, repos, rev, include_revprops,
+                                    authz_func, &authz_baton, iterpool));
 
       /* When dumping revision 0, we just write out the revision record.
          The parser might want to use its properties.
@@ -2091,8 +2134,7 @@ svn_repos_dump_fs4(svn_repos_t *repos,
           SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
                                        to_root, "",
                                        dump_editor, dump_edit_baton,
-                                       NULL,
-                                       NULL,
+                                       authz_func, &authz_baton,
                                        FALSE, /* don't send text-deltas */
                                        svn_depth_infinity,
                                        FALSE, /* don't send entry props */
@@ -2104,7 +2146,7 @@ svn_repos_dump_fs4(svn_repos_t *repos,
           /* The normal case: compare consecutive revs. */
           SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
                                     dump_editor, dump_edit_baton,
-                                    NULL, NULL, iterpool));
+                                    authz_func, &authz_baton, iterpool));
 
           /* While our editor close_edit implementation is a no-op, we still
              do this for completeness. */

Modified: subversion/branches/addremove/subversion/libsvn_repos/fs-wrap.c
URL: http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_repos/fs-wrap.c?rev=1813173&r1=1813172&r2=1813173&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_repos/fs-wrap.c (original)
+++ subversion/branches/addremove/subversion/libsvn_repos/fs-wrap.c Tue Oct 24 12:40:47 2017
@@ -33,6 +33,7 @@
 #include "svn_repos.h"
 #include "svn_time.h"
 #include "svn_sorts.h"
+#include "svn_subst.h"
 #include "repos.h"
 #include "svn_private_config.h"
 #include "private/svn_repos_private.h"
@@ -235,10 +236,13 @@ svn_repos__validate_prop(const char *nam
            * carriage return characters ('\r'). */
           if (strchr(value->data, '\r') != NULL)
             {
-              return svn_error_createf
-                (SVN_ERR_BAD_PROPERTY_VALUE, NULL,
+              svn_error_t *err = svn_error_createf
+                (SVN_ERR_BAD_PROPERTY_VALUE_EOL, NULL,
                  _("Cannot accept non-LF line endings in '%s' property"),
                    name);
+
+              return svn_error_create(SVN_ERR_BAD_PROPERTY_VALUE, err,
+                                      _("Invalid property value"));
             }
         }
 
@@ -256,6 +260,34 @@ svn_repos__validate_prop(const char *nam
     }
 
   return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_repos__normalize_prop(const svn_string_t **result_p,
+                          svn_boolean_t *normalized_p,
+                          const char *name,
+                          const svn_string_t *value,
+                          apr_pool_t *result_pool,
+                          apr_pool_t *scratch_pool)
+{
+  if (svn_prop_needs_translation(name) && value)
+    {
+      svn_string_t *new_value;
+
+      SVN_ERR(svn_subst_translate_string2(&new_value, NULL, normalized_p,
+                                          value, "UTF-8", TRUE,
+                                          result_pool, scratch_pool));
+      *result_p = new_value;
+    }
+  else
+    {
+      *result_p = svn_string_dup(value, result_pool);
+      if (normalized_p)
+        *normalized_p = FALSE;
+    }
+
+  return SVN_NO_ERROR;
 }
 
 

Modified: subversion/branches/addremove/subversion/libsvn_repos/hooks.c
URL: http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_repos/hooks.c?rev=1813173&r1=1813172&r2=1813173&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_repos/hooks.c (original)
+++ subversion/branches/addremove/subversion/libsvn_repos/hooks.c Tue Oct 24 12:40:47 2017
@@ -476,11 +476,8 @@ svn_repos__hooks_start_commit(svn_repos_
 
       if (capabilities)
         {
-          capabilities_string = svn_cstring_join(capabilities, ":", pool);
-
-          /* Get rid of that annoying final colon. */
-          if (capabilities_string[0])
-            capabilities_string[strlen(capabilities_string) - 1] = '\0';
+          capabilities_string = svn_cstring_join2(capabilities, ":",
+                                                  FALSE, pool);
         }
       else
         {
@@ -799,8 +796,8 @@ svn_repos__hooks_post_lock(svn_repos_t *
     {
       const char *args[5];
       apr_file_t *stdin_handle = NULL;
-      svn_string_t *paths_str = svn_string_create(svn_cstring_join
-                                                  (paths, "\n", pool),
+      svn_string_t *paths_str = svn_string_create(svn_cstring_join2
+                                                  (paths, "\n", TRUE, pool),
                                                   pool);
 
       SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool));
@@ -875,8 +872,8 @@ svn_repos__hooks_post_unlock(svn_repos_t
     {
       const char *args[5];
       apr_file_t *stdin_handle = NULL;
-      svn_string_t *paths_str = svn_string_create(svn_cstring_join
-                                                  (paths, "\n", pool),
+      svn_string_t *paths_str = svn_string_create(svn_cstring_join2
+                                                  (paths, "\n", TRUE, pool),
                                                   pool);
 
       SVN_ERR(create_temp_file(&stdin_handle, paths_str, pool));

Modified: subversion/branches/addremove/subversion/libsvn_repos/list.c
URL: http://svn.apache.org/viewvc/subversion/branches/addremove/subversion/libsvn_repos/list.c?rev=1813173&r1=1813172&r2=1813173&view=diff
==============================================================================
--- subversion/branches/addremove/subversion/libsvn_repos/list.c (original)
+++ subversion/branches/addremove/subversion/libsvn_repos/list.c Tue Oct 24 12:40:47 2017
@@ -30,6 +30,7 @@
 
 #include "private/svn_repos_private.h"
 #include "private/svn_sorts_private.h"
+#include "private/svn_utf_private.h"
 #include "svn_private_config.h" /* for SVN_TEMPLATE_ROOT_DIR */
 
 #include "repos.h"
@@ -91,23 +92,16 @@ svn_repos_stat(svn_dirent_t **dirent,
 }
 
 /* Return TRUE of DIRNAME matches any of the const char * in PATTERNS.
- * Note that any DIRNAME will match if PATTERNS is empty. */
+ * Note that any DIRNAME will match if PATTERNS is empty.
+ * Use SCRATCH_BUFFER for temporary string contents. */
 static svn_boolean_t
 matches_any(const char *dirname,
-            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, dirname, APR_FNM_PERIOD) == APR_SUCCESS)
-        return TRUE;
-    }
-
-  return FALSE;
+  return patterns
+       ? svn_utf__fuzzy_glob_match(dirname, patterns, scratch_buffer)
+       : TRUE;
 }
 
 /* Utility to prevent code duplication.
@@ -166,6 +160,8 @@ compare_filtered_dirent(const void *lhs,
  *
  * However, DEPTH is not svn_depth_empty and PATH has already been reported.
  * Therefore, we can call this recursively.
+ *
+ * Uses SCRATCH_BUFFER for temporary string contents.
  */
 static svn_error_t *
 do_list(svn_fs_root_t *root,
@@ -179,6 +175,7 @@ do_list(svn_fs_root_t *root,
         void *receiver_baton,
         svn_cancel_func_t cancel_func,
         void *cancel_baton,
+        svn_membuf_t *scratch_buffer,
         apr_pool_t *scratch_pool)
 {
   apr_hash_t *entries;
@@ -210,7 +207,8 @@ do_list(svn_fs_root_t *root,
         continue;
 
       /* We can skip files that don't match any of the search patterns. */
-      filtered.is_match = matches_any(filtered.dirent->name, patterns);
+      filtered.is_match = matches_any(filtered.dirent->name, patterns,
+                                      scratch_buffer);
       if (!filtered.is_match && filtered.dirent->kind == svn_node_file)
         continue;
 
@@ -258,7 +256,7 @@ do_list(svn_fs_root_t *root,
         SVN_ERR(do_list(root, sub_path, patterns, svn_depth_infinity,
                         path_info_only, authz_read_func, authz_read_baton,
                         receiver, receiver_baton, cancel_func,
-                        cancel_baton, iterpool));
+                        cancel_baton, scratch_buffer, iterpool));
     }
 
   svn_pool_destroy(iterpool);
@@ -280,6 +278,8 @@ svn_repos_list(svn_fs_root_t *root,
                void *cancel_baton,
                apr_pool_t *scratch_pool)
 {
+  svn_membuf_t scratch_buffer;
+
   /* Parameter check. */
   svn_node_kind_t kind;
   if (depth < svn_depth_empty)
@@ -317,8 +317,13 @@ svn_repos_list(svn_fs_root_t *root,
   if (patterns && patterns->nelts == 0)
     return SVN_NO_ERROR;
 
+  /* We need a scratch buffer for temporary string data.
+   * Create one with a reasonable initial size. */
+  svn_membuf__create(&scratch_buffer, 256, scratch_pool);
+
   /* Actually report PATH, if it passes the filters. */
-  if (matches_any(svn_dirent_dirname(path, scratch_pool), patterns))
+  if (matches_any(svn_dirent_dirname(path, scratch_pool), patterns,
+                  &scratch_buffer))
     SVN_ERR(report_dirent(root, path, kind, path_info_only,
                           receiver, receiver_baton, scratch_pool));
 
@@ -327,7 +332,7 @@ svn_repos_list(svn_fs_root_t *root,
     SVN_ERR(do_list(root, path, patterns, depth,
                     path_info_only, authz_read_func, authz_read_baton,
                     receiver, receiver_baton, cancel_func, cancel_baton,
-                    scratch_pool));
+                    &scratch_buffer, scratch_pool));
 
   return SVN_NO_ERROR;
 }



Mime
View raw message