You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by hw...@apache.org on 2011/12/19 19:49:43 UTC

svn commit: r1220893 [5/19] - in /subversion/branches/fs-py: ./ build/ build/ac-macros/ build/generator/ build/generator/templates/ build/win32/ contrib/client-side/emacs/ contrib/server-side/mod_dontdothat/ notes/ subversion/bindings/javahl/native/ su...

Modified: subversion/branches/fs-py/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/fs-py/subversion/libsvn_client/merge.c?rev=1220893&r1=1220892&r2=1220893&view=diff
==============================================================================
--- subversion/branches/fs-py/subversion/libsvn_client/merge.c (original)
+++ subversion/branches/fs-py/subversion/libsvn_client/merge.c Mon Dec 19 18:49:34 2011
@@ -52,9 +52,11 @@
 #include "client.h"
 #include "mergeinfo.h"
 
+#include "private/svn_opt_private.h"
 #include "private/svn_wc_private.h"
 #include "private/svn_mergeinfo_private.h"
 #include "private/svn_fspath.h"
+#include "private/svn_ra_private.h"
 
 #include "svn_private_config.h"
 
@@ -292,6 +294,13 @@ typedef struct merge_cmd_baton_t {
 
 /*** Utilities ***/
 
+/* Repository root and UUID for a repository. */
+typedef struct url_uuid_t
+{
+  const char *url;
+  const char *uuid;
+} url_uuid_t;
+
 /* Return SVN_ERR_UNSUPPORTED_FEATURE if URL is not inside the repository
    of LOCAL_ABSPATH.  Use SCRATCH_POOL for temporary allocations. */
 static svn_error_t *
@@ -309,6 +318,42 @@ check_repos_match(merge_cmd_baton_t *mer
 
   return SVN_NO_ERROR;
 }
+
+/* Return TRUE iff the repository identified by REPOS_ROOT1 is the same as
+ * that identified by REPOS_ROOT2.  If STRICT_URLS is true, the URLs must
+ * match (and the UUIDs, just to be sure), otherwise just the UUIDs must
+ * match and the URLs can differ (a common case is http versus https). */
+static svn_boolean_t
+is_same_repos(const url_uuid_t *repos_root1,
+              const url_uuid_t *repos_root2,
+              svn_boolean_t strict_urls)
+{
+  if (strict_urls)
+    return (strcmp(repos_root1->url, repos_root2->url) == 0
+            && strcmp(repos_root1->uuid, repos_root2->uuid) == 0);
+  else
+    return (strcmp(repos_root1->uuid, repos_root2->uuid) == 0);
+}
+
+/* If the repository identified by REPOS_ROOT1 is not the same as that
+ * identified by REPOS_ROOT2, throw a SVN_ERR_CLIENT_UNRELATED_RESOURCES
+ * error mentioning PATH1 and PATH2. For STRICT_URLS, see is_same_repos().
+ */
+static svn_error_t *
+check_same_repos(const url_uuid_t *repos_root1,
+                 const char *path1,
+                 const url_uuid_t *repos_root2,
+                 const char *path2,
+                 svn_boolean_t strict_urls,
+                 apr_pool_t *scratch_pool)
+{
+  if (! is_same_repos(repos_root1, repos_root2, strict_urls))
+    return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
+                             _("'%s' must be from the same repository as "
+                               "'%s'"), path1, path2);
+  return SVN_NO_ERROR;
+}
+
 /* Return true iff we're in dry-run mode and WCPATH would have been
    deleted by now if we weren't in dry-run mode.
    Used to avoid spurious notifications (e.g. conflicts) from a merge
@@ -457,31 +502,23 @@ make_conflict_versions(const svn_wc_conf
 
   /* Construct the source URLs of the victim. */
   {
-    const char *child = svn_dirent_is_child(merge_b->target_abspath,
-                                            victim_abspath,
-                                            merge_b->pool);
-    if (child != NULL)
-      {
-        left_url = svn_path_url_add_component2(merge_b->merge_source.url1,
-                                               child, merge_b->pool);
-        right_url = svn_path_url_add_component2(merge_b->merge_source.url2,
-                                                child, merge_b->pool);
-      }
-    else
-      {
-        left_url = merge_b->merge_source.url1;
-        right_url = merge_b->merge_source.url2;
-      }
+    const char *child = svn_dirent_skip_ancestor(merge_b->target_abspath,
+                                                 victim_abspath);
+    SVN_ERR_ASSERT(child != NULL);
+    left_url = svn_path_url_add_component2(merge_b->merge_source.url1,
+                                           child, merge_b->pool);
+    right_url = svn_path_url_add_component2(merge_b->merge_source.url2,
+                                            child, merge_b->pool);
   }
 
   *left = svn_wc_conflict_version_create(
             src_repos_url,
-            svn_uri__is_child(src_repos_url, left_url, merge_b->pool),
+            svn_uri_skip_ancestor(src_repos_url, left_url, merge_b->pool),
             merge_b->merge_source.rev1, node_kind, merge_b->pool);
 
   *right = svn_wc_conflict_version_create(
              src_repos_url,
-             svn_uri__is_child(src_repos_url, right_url, merge_b->pool),
+             svn_uri_skip_ancestor(src_repos_url, right_url, merge_b->pool),
              merge_b->merge_source.rev2, node_kind, merge_b->pool);
 
   return SVN_NO_ERROR;
@@ -536,7 +573,6 @@ tree_conflict(merge_cmd_baton_t *merge_b
               svn_wc_conflict_reason_t reason)
 {
   const svn_wc_conflict_description2_t *existing_conflict;
-  svn_wc_conflict_description2_t *conflict;
 
   if (merge_b->record_only || merge_b->dry_run)
     return SVN_NO_ERROR;
@@ -546,6 +582,8 @@ tree_conflict(merge_cmd_baton_t *merge_b
                                     merge_b->pool));
   if (existing_conflict == NULL)
     {
+      svn_wc_conflict_description2_t *conflict;
+
       /* There is no existing tree conflict so it is safe to add one. */
       SVN_ERR(make_tree_conflict(&conflict, merge_b, victim_abspath,
                                  node_kind, action, reason));
@@ -554,6 +592,7 @@ tree_conflict(merge_cmd_baton_t *merge_b
 
       if (merge_b->conflicted_paths == NULL)
         merge_b->conflicted_paths = apr_hash_make(merge_b->pool);
+      victim_abspath = apr_pstrdup(merge_b->pool, victim_abspath);
 
       apr_hash_set(merge_b->conflicted_paths, victim_abspath,
                    APR_HASH_KEY_STRING, victim_abspath);
@@ -595,6 +634,7 @@ tree_conflict_on_add(merge_cmd_baton_t *
 
       if (merge_b->conflicted_paths == NULL)
         merge_b->conflicted_paths = apr_hash_make(merge_b->pool);
+      victim_abspath = apr_pstrdup(merge_b->pool, victim_abspath);
 
       apr_hash_set(merge_b->conflicted_paths, victim_abspath,
                    APR_HASH_KEY_STRING, victim_abspath);
@@ -625,6 +665,7 @@ tree_conflict_on_add(merge_cmd_baton_t *
 
       if (merge_b->conflicted_paths == NULL)
         merge_b->conflicted_paths = apr_hash_make(merge_b->pool);
+      victim_abspath = apr_pstrdup(merge_b->pool, victim_abspath);
 
       apr_hash_set(merge_b->conflicted_paths, victim_abspath,
                    APR_HASH_KEY_STRING, victim_abspath);
@@ -753,7 +794,7 @@ omit_mergeinfo_changes(apr_array_header_
 /* Helper for merge_props_changed().
 
    *PROPS is an array of svn_prop_t structures representing regular properties
-   to be added to the working copy LOCAL_ABSPATH.
+   to be added to the working copy TARGET_ABSPATH.
 
    HONOR_MERGEINFO determines whether mergeinfo will be honored by this
    function (when applicable).
@@ -762,14 +803,14 @@ omit_mergeinfo_changes(apr_array_header_
    REINTEGRATE_MERGE is FALSE do nothing.  Otherwise, if
    SAME_REPOS is false, then filter out all mergeinfo
    property additions (Issue #3383) from *PROPS.  If SAME_REPOS is
-   true then filter out mergeinfo property additions to LOCAL_ABSPATH when
-   those additions refer to the same line of history as LOCAL_ABSPATH as
+   true then filter out mergeinfo property additions to TARGET_ABSPATH when
+   those additions refer to the same line of history as TARGET_ABSPATH as
    described below.
 
    If mergeinfo is being honored and SAME_REPOS is true
    then examine the added mergeinfo, looking at each range (or single rev)
    of each source path.  If a source_path/range refers to the same line of
-   history as LOCAL_ABSPATH (pegged at its base revision), then filter out
+   history as TARGET_ABSPATH (pegged at its base revision), then filter out
    that range.  If the entire rangelist for a given path is filtered then
    filter out the path as well.
 
@@ -778,9 +819,9 @@ omit_mergeinfo_changes(apr_array_header_
 
    If any filtering occurs, set outgoing *PROPS to a shallow copy (allocated
    in POOL) of incoming *PROPS minus the filtered mergeinfo. */
-static svn_error_t*
+static svn_error_t *
 filter_self_referential_mergeinfo(apr_array_header_t **props,
-                                  const char *local_abspath,
+                                  const char *target_abspath,
                                   svn_boolean_t honor_mergeinfo,
                                   svn_boolean_t same_repos,
                                   svn_boolean_t reintegrate_merge,
@@ -792,7 +833,8 @@ filter_self_referential_mergeinfo(apr_ar
   int i;
   apr_pool_t *iterpool;
   svn_boolean_t is_added;
-  svn_revnum_t base_revision;
+  const char *target_base_url;
+  svn_revnum_t target_base_rev;
 
   /* Issue #3383: We don't want mergeinfo from a foreign repos.
 
@@ -816,12 +858,14 @@ filter_self_referential_mergeinfo(apr_ar
 
   /* If this is a merge from the same repository and PATH itself has been
      added there is no need to filter. */
-  SVN_ERR(svn_wc__node_is_added(&is_added, ctx->wc_ctx, local_abspath, pool));
+  SVN_ERR(svn_wc__node_is_added(&is_added, ctx->wc_ctx, target_abspath, pool));
   if (is_added)
     return SVN_NO_ERROR;
 
-  SVN_ERR(svn_wc__node_get_base_rev(&base_revision, ctx->wc_ctx,
-                                    local_abspath, pool));
+  SVN_ERR(svn_client_url_from_path2(&target_base_url, target_abspath,
+                                    ctx, pool, pool));
+  SVN_ERR(svn_wc__node_get_base_rev(&target_base_rev, ctx->wc_ctx,
+                                    target_abspath, pool));
 
   adjusted_props = apr_array_make(pool, (*props)->nelts, sizeof(svn_prop_t));
   iterpool = svn_pool_create(pool);
@@ -832,7 +876,6 @@ filter_self_referential_mergeinfo(apr_ar
       svn_mergeinfo_t mergeinfo, younger_mergeinfo;
       svn_mergeinfo_t filtered_mergeinfo = NULL;
       svn_mergeinfo_t filtered_younger_mergeinfo = NULL;
-      const char *target_url;
       const char *old_url = NULL;
       svn_error_t *err;
 
@@ -849,11 +892,9 @@ filter_self_referential_mergeinfo(apr_ar
       /* Non-empty mergeinfo; filter self-referential mergeinfo out. */
       /* Temporarily reparent our RA session to the merge
          target's URL. */
-      SVN_ERR(svn_client_url_from_path2(&target_url, local_abspath,
-                                        ctx, iterpool, iterpool));
       SVN_ERR(svn_client__ensure_ra_session_url(&old_url,
                                                 ra_session,
-                                                target_url, iterpool));
+                                                target_base_url, iterpool));
 
       /* Parse the incoming mergeinfo to allow easier manipulation. */
       err = svn_mergeinfo_parse(&mergeinfo, prop->value->data, iterpool);
@@ -906,7 +947,7 @@ filter_self_referential_mergeinfo(apr_ar
          the cost of a roundtrip communication with the repository. */
       SVN_ERR(split_mergeinfo_on_revision(&younger_mergeinfo,
                                           &mergeinfo,
-                                          base_revision,
+                                          target_base_rev,
                                           iterpool));
 
       /* Filter self-referential mergeinfo from younger_mergeinfo. */
@@ -935,38 +976,22 @@ filter_self_referential_mergeinfo(apr_ar
               for (j = 0; j < rangelist->nelts; j++)
                 {
                   svn_error_t *err2;
-                  svn_opt_revision_t *start_revision;
                   const char *start_url;
-                  svn_opt_revision_t peg_rev, rev1_opt;
                   svn_merge_range_t *range =
                     APR_ARRAY_IDX(rangelist, j, svn_merge_range_t *);
 
-                  peg_rev.kind = svn_opt_revision_number;
-                  peg_rev.value.number = base_revision;
-                  rev1_opt.kind = svn_opt_revision_number;
-                  /* SVN_PROP_MERGEINFO only stores forward merges, so
-                     the start range of svn_merge_range_t RANGE is not
-                     inclusive. */
-                  rev1_opt.value.number = range->start + 1;
-
                   /* Because the merge source normalization code
                      ensures mergeinfo refers to real locations on
                      the same line of history, there's no need to
                      look at the whole range, just the start. */
 
                   /* Check if PATH@BASE_REVISION exists at
-                     RANGE->START on the same line of history. */
-                  err2 = svn_client__repos_locations(&start_url,
-                                                     &start_revision,
-                                                     NULL,
-                                                     NULL,
-                                                     ra_session,
-                                                     target_url,
-                                                     &peg_rev,
-                                                     &rev1_opt,
-                                                     NULL,
-                                                     ctx,
-                                                     iterpool);
+                     RANGE->START on the same line of history.
+                     (start+1 because RANGE->start is not inclusive.) */
+                  err2 = svn_client__repos_location(&start_url, ra_session,
+                                                    target_base_url, target_base_rev,
+                                                    range->start + 1,
+                                                    ctx, iterpool, iterpool);
                   if (err2)
                     {
                       if (err2->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES
@@ -1041,7 +1066,7 @@ filter_self_referential_mergeinfo(apr_ar
 
           SVN_ERR(svn_client__get_history_as_mergeinfo(
             &implicit_mergeinfo, NULL,
-            base_revision, base_revision, SVN_INVALID_REVNUM,
+            target_base_rev, target_base_rev, SVN_INVALID_REVNUM,
             ra_session,
             ctx,
             iterpool));
@@ -1102,7 +1127,6 @@ merge_props_changed(svn_wc_notify_state_
   apr_array_header_t *props;
   merge_cmd_baton_t *merge_b = baton;
   svn_client_ctx_t *ctx = merge_b->ctx;
-  svn_error_t *err;
 
   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
 
@@ -1134,6 +1158,8 @@ merge_props_changed(svn_wc_notify_state_
      definition, 'svn merge' shouldn't touch any data within .svn/  */
   if (props->nelts)
     {
+      svn_error_t *err;
+
       /* If this is a forward merge then don't add new mergeinfo to
          PATH that is already part of PATH's own history, see
          http://svn.haxx.se/dev/archive-2008-09/0006.shtml.  If the
@@ -1179,8 +1205,9 @@ merge_props_changed(svn_wc_notify_state_
                                                     scratch_pool,
                                                     scratch_pool));
 
-                  if (apr_hash_get(pristine_props, SVN_PROP_MERGEINFO,
-                                   APR_HASH_KEY_STRING))
+                  if (pristine_props
+                      && apr_hash_get(pristine_props, SVN_PROP_MERGEINFO,
+                                      APR_HASH_KEY_STRING))
                     has_pristine_mergeinfo = TRUE;
 
                   if (!has_pristine_mergeinfo && prop->value)
@@ -1227,6 +1254,8 @@ merge_props_changed(svn_wc_notify_state_
       else if (err)
         return svn_error_trace(err);
     }
+  else if (state)
+    *state = svn_wc_notify_state_unchanged;
 
   return SVN_NO_ERROR;
 }
@@ -1286,8 +1315,8 @@ typedef struct conflict_resolver_baton_t
      resolution attempt was made. */
   apr_hash_t **conflicted_paths;
 
-  /* Pool used in notification_receiver() to avoid the iteration
-     sub-pool which is passed in, then subsequently destroyed. */
+  /* Pool with a sufficient lifetime to be used for output members such as
+   * *CONFLICTED_PATHS. */
   apr_pool_t *pool;
 } conflict_resolver_baton_t;
 
@@ -1428,7 +1457,6 @@ merge_file_changed(svn_wc_notify_state_t
   merge_cmd_baton_t *merge_b = baton;
   const char *mine_abspath = svn_dirent_join(merge_b->target_abspath,
                                              mine_relpath, scratch_pool);
-  enum svn_wc_merge_outcome_t merge_outcome;
   svn_node_kind_t wc_kind;
   svn_boolean_t is_deleted;
 
@@ -1436,6 +1464,9 @@ merge_file_changed(svn_wc_notify_state_t
   SVN_ERR_ASSERT(!older_abspath || svn_dirent_is_absolute(older_abspath));
   SVN_ERR_ASSERT(!yours_abspath || svn_dirent_is_absolute(yours_abspath));
 
+  if (tree_conflicted)
+    *tree_conflicted = FALSE;
+
   /* Check for an obstructed or missing node on disk. */
   {
     svn_wc_notify_state_t obstr_state;
@@ -1582,6 +1613,7 @@ merge_file_changed(svn_wc_notify_state_t
   if (older_abspath)
     {
       svn_boolean_t has_local_mods;
+      enum svn_wc_merge_outcome_t merge_outcome;
 
       /* xgettext: the '.working', '.merge-left.r%ld' and
          '.merge-right.r%ld' strings are used to tag onto a file
@@ -1665,6 +1697,9 @@ merge_file_added(svn_wc_notify_state_t *
 
   SVN_ERR_ASSERT(svn_dirent_is_absolute(mine_abspath));
 
+  if (tree_conflicted)
+    *tree_conflicted = FALSE;
+
   /* Easy out: We are only applying mergeinfo differences. */
   if (merge_b->record_only)
     {
@@ -1754,14 +1789,12 @@ merge_file_added(svn_wc_notify_state_t *
             if (merge_b->same_repos)
               {
                 const char *child =
-                  svn_dirent_is_child(merge_b->target_abspath, mine_abspath,
-                                      scratch_pool);
-                if (child != NULL)
-                  copyfrom_url = svn_path_url_add_component2(
-                                               merge_b->merge_source.url2,
-                                               child, scratch_pool);
-                else
-                  copyfrom_url = merge_b->merge_source.url2;
+                  svn_dirent_skip_ancestor(merge_b->target_abspath,
+                                           mine_abspath);
+                SVN_ERR_ASSERT(child != NULL);
+                copyfrom_url = svn_path_url_add_component2(
+                                             merge_b->merge_source.url2,
+                                             child, scratch_pool);
                 copyfrom_rev = rev2;
                 SVN_ERR(check_repos_match(merge_b, mine_abspath, copyfrom_url,
                                           scratch_pool));
@@ -1954,9 +1987,7 @@ files_same_p(svn_boolean_t *same,
     {
       svn_stream_t *mine_stream;
       svn_stream_t *older_stream;
-      svn_opt_revision_t working_rev;
-
-      working_rev.kind = svn_opt_revision_working;
+      svn_opt_revision_t working_rev = { svn_opt_revision_working, { 0 } };
 
       /* Compare the file content, translating 'mine' to 'normal' form. */
       SVN_ERR(svn_client__get_normalized_stream(&mine_stream, wc_ctx,
@@ -1992,8 +2023,9 @@ merge_file_deleted(svn_wc_notify_state_t
   const char *mine_abspath = svn_dirent_join(merge_b->target_abspath,
                                              mine_relpath, scratch_pool);
   svn_node_kind_t kind;
-  svn_boolean_t moved_away;
-  svn_wc_conflict_reason_t reason;
+
+  if (tree_conflicted)
+    *tree_conflicted = FALSE;
 
   if (merge_b->dry_run)
     {
@@ -2081,21 +2113,26 @@ merge_file_deleted(svn_wc_notify_state_t
         *state = svn_wc_notify_state_obstructed;
       break;
     case svn_node_none:
-      /* The file deleted in the diff does not exist at the current URL.
-       *
-       * This is use case 6 described in the paper attached to issue
-       * #2282.  See also notes/tree-conflicts/detection.txt
-       */
-      SVN_ERR(check_moved_away(&moved_away, merge_b->ctx->wc_ctx,
-                               mine_abspath, scratch_pool));
-      reason = moved_away ? svn_wc_conflict_reason_moved_away
-                          : svn_wc_conflict_reason_deleted;
-      SVN_ERR(tree_conflict(merge_b, mine_abspath, svn_node_file,
-                            svn_wc_conflict_action_delete, reason));
-      if (tree_conflicted)
-        *tree_conflicted = TRUE;
-      if (state)
-        *state = svn_wc_notify_state_missing;
+      {
+        svn_boolean_t moved_away;
+        svn_wc_conflict_reason_t reason;
+
+        /* The file deleted in the diff does not exist at the current URL.
+         *
+         * This is use case 6 described in the paper attached to issue
+         * #2282.  See also notes/tree-conflicts/detection.txt
+         */
+        SVN_ERR(check_moved_away(&moved_away, merge_b->ctx->wc_ctx,
+                                 mine_abspath, scratch_pool));
+        reason = moved_away ? svn_wc_conflict_reason_moved_away
+                            : svn_wc_conflict_reason_deleted;
+        SVN_ERR(tree_conflict(merge_b, mine_abspath, svn_node_file,
+                              svn_wc_conflict_action_delete, reason));
+        if (tree_conflicted)
+          *tree_conflicted = TRUE;
+        if (state)
+          *state = svn_wc_notify_state_missing;
+      }
       break;
     default:
       if (state)
@@ -2324,12 +2361,8 @@ merge_dir_deleted(svn_wc_notify_state_t 
   const char *local_abspath = svn_dirent_join(merge_b->target_abspath,
                                               local_relpath, scratch_pool);
   svn_node_kind_t kind;
-  svn_error_t *err;
   svn_boolean_t is_versioned;
   svn_boolean_t is_deleted;
-  svn_boolean_t moved_away;
-  svn_wc_conflict_reason_t reason;
-
 
   /* Easy out: We are only applying mergeinfo differences. */
   if (merge_b->record_only)
@@ -2376,6 +2409,8 @@ merge_dir_deleted(svn_wc_notify_state_t 
       {
         if (is_versioned && !is_deleted)
           {
+            svn_error_t *err;
+
             /* ### TODO: Before deleting, we should ensure that this dir
                tree is equal to the one we're being asked to delete.
                If not, mark this directory as a tree conflict victim,
@@ -2412,6 +2447,9 @@ merge_dir_deleted(svn_wc_notify_state_t 
           }
         else
           {
+            svn_boolean_t moved_away;
+            svn_wc_conflict_reason_t reason;
+
             /* Dir is already not under version control at this path. */
             /* Raise a tree conflict. */
             SVN_ERR(check_moved_away(&moved_away, merge_b->ctx->wc_ctx,
@@ -2430,19 +2468,24 @@ merge_dir_deleted(svn_wc_notify_state_t 
         *state = svn_wc_notify_state_obstructed;
       break;
     case svn_node_none:
-      /* Dir is already non-existent. This is use case 6 as described in
-       * notes/tree-conflicts/detection.txt.
-       * This case was formerly treated as no-op. */
-      SVN_ERR(check_moved_away(&moved_away, merge_b->ctx->wc_ctx,
-                               local_abspath, scratch_pool));
-      reason = moved_away ? svn_wc_conflict_reason_moved_away
-                          : svn_wc_conflict_reason_deleted;
-      SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_dir,
-                            svn_wc_conflict_action_delete, reason));
-      if (tree_conflicted)
-        *tree_conflicted = TRUE;
-      if (state)
-        *state = svn_wc_notify_state_missing;
+      {
+        svn_boolean_t moved_away;
+        svn_wc_conflict_reason_t reason;
+
+        /* Dir is already non-existent. This is use case 6 as described in
+         * notes/tree-conflicts/detection.txt.
+         * This case was formerly treated as no-op. */
+        SVN_ERR(check_moved_away(&moved_away, merge_b->ctx->wc_ctx,
+                                 local_abspath, scratch_pool));
+        reason = moved_away ? svn_wc_conflict_reason_moved_away
+                            : svn_wc_conflict_reason_deleted;
+        SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_dir,
+                              svn_wc_conflict_action_delete, reason));
+        if (tree_conflicted)
+          *tree_conflicted = TRUE;
+        if (state)
+          *state = svn_wc_notify_state_missing;
+      }
       break;
     default:
       if (state)
@@ -2466,7 +2509,6 @@ merge_dir_opened(svn_boolean_t *tree_con
   merge_cmd_baton_t *merge_b = baton;
   const char *local_abspath = svn_dirent_join(merge_b->target_abspath,
                                               local_relpath, scratch_pool);
-  svn_depth_t parent_depth;
   svn_node_kind_t wc_kind;
   svn_wc_notify_state_t obstr_state;
   svn_boolean_t is_deleted;
@@ -2491,6 +2533,8 @@ merge_dir_opened(svn_boolean_t *tree_con
     {
       if (wc_kind == svn_node_none)
         {
+          svn_depth_t parent_depth;
+
           /* If the parent is too shallow to contain this directory,
            * and the directory is not present on disk, skip it.
            * Non-inheritable mergeinfo will be recorded, allowing
@@ -2598,9 +2642,6 @@ typedef struct notification_receiver_bat
   svn_wc_notify_func2_t wrapped_func;
   void *wrapped_baton;
 
-  /* The number of notifications received. */
-  apr_uint32_t nbr_notifications;
-
   /* The number of operative notifications received. */
   apr_uint32_t nbr_operative_notifications;
 
@@ -2634,15 +2675,15 @@ typedef struct notification_receiver_bat
      merge notification begin lines. */
   apr_array_header_t *children_with_mergeinfo;
 
-  /* The index in CHILDREN_WITH_MERGEINFO where we found the nearest ancestor
-     for merged path. Default value is '-1'.*/
-  int cur_ancestor_index;
+  /* The path in CHILDREN_WITH_MERGEINFO where we found the nearest ancestor
+     for merged path. Default value is null. */
+  const char *cur_ancestor_abspath;
 
   /* We use this to make a decision on merge begin line notifications. */
   merge_cmd_baton_t *merge_b;
 
-  /* Pool used in notification_receiver() to avoid the iteration
-     sub-pool which is passed in, then subsequently destroyed. */
+  /* Pool with a sufficient lifetime to be used for output members such as
+   * MERGED_ABSPATHS. */
   apr_pool_t *pool;
 
 } notification_receiver_baton_t;
@@ -2654,22 +2695,17 @@ typedef struct notification_receiver_bat
    then child->abspath must be a proper ancestor of PATH.
 
    CHILDREN_WITH_MERGEINFO is expected to be sorted in Depth first
-   order of path. Nearest ancestor's index from
-   CHILDREN_WITH_MERGEINFO is returned. */
-static int
+   order of path. */
+static svn_client__merge_path_t *
 find_nearest_ancestor(const apr_array_header_t *children_with_mergeinfo,
                       svn_boolean_t path_is_own_ancestor,
                       const char *path)
 {
   int i;
-  int ancestor_index = 0;
+  svn_client__merge_path_t *ancestor = NULL;
+
+  SVN_ERR_ASSERT_NO_RETURN(children_with_mergeinfo != NULL);
 
-  /* This if condition is not needed as this function should be used
-     from the context of same_url merge where CHILDREN_WITH_MERGEINFO
-     will not be NULL and of size atleast 1. We have this if condition
-     just to protect the wrong caller. */
-  if (!children_with_mergeinfo)
-    return 0;
   for (i = 0; i < children_with_mergeinfo->nelts; i++)
     {
       svn_client__merge_path_t *child =
@@ -2677,12 +2713,86 @@ find_nearest_ancestor(const apr_array_he
       if (svn_dirent_is_ancestor(child->abspath, path)
           && (path_is_own_ancestor
               || svn_path_compare_paths(child->abspath, path) != 0))
-        ancestor_index = i;
+        ancestor = child;
+    }
+  return ancestor;
+}
+
+
+/* Notify that we're starting to record the merge of the
+ * revision range RANGE into TARGET_ABSPATH.  RANGE should be null if the
+ * merge sources are not from the same URL.
+ *
+ * This calls the client's notification receiver (as found in the client
+ * context), with a WC abspath.
+ */
+static void
+notify_merge_begin(const char *target_abspath,
+                   const svn_merge_range_t *range,
+                   merge_cmd_baton_t *merge_b,
+                   apr_pool_t *pool)
+{
+  if (merge_b->ctx->notify_func2)
+    {
+      svn_wc_notify_t *n
+        = svn_wc_create_notify(target_abspath,
+                               merge_b->same_repos
+                               ? svn_wc_notify_merge_begin
+                               : svn_wc_notify_foreign_merge_begin,
+                               pool);
+
+      n->merge_range = range ? svn_merge_range_dup(range, pool) : NULL;
+      merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, n, pool);
+    }
+}
+
+/* Notify that we're starting to record mergeinfo for the merge of the
+ * revision range RANGE into TARGET_ABSPATH.  RANGE should be null if the
+ * merge sources are not from the same URL.
+ *
+ * This calls the client's notification receiver (as found in the client
+ * context), with a WC abspath.
+ */
+static void
+notify_mergeinfo_recording(const char *target_abspath,
+                           const svn_merge_range_t *range,
+                           svn_client_ctx_t *ctx,
+                           apr_pool_t *pool)
+{
+  if (ctx->notify_func2)
+    {
+      svn_wc_notify_t *n = svn_wc_create_notify(
+        target_abspath, svn_wc_notify_merge_record_info_begin, pool);
+
+      n->merge_range = range ? svn_merge_range_dup(range, pool) : NULL;
+      ctx->notify_func2(ctx->notify_baton2, n, pool);
     }
-  return ancestor_index;
 }
 
+/* Notify that we're completing the merge into TARGET_ABSPATH.
+ *
+ * This calls the client's notification receiver (as found in the client
+ * context), with a WC abspath.
+ */
+static void
+notify_merge_completed(const char *target_abspath,
+                       svn_client_ctx_t *ctx,
+                       apr_pool_t *pool)
+{
+  if (ctx->notify_func2)
+    {
+      svn_wc_notify_t *n
+        = svn_wc_create_notify(target_abspath, svn_wc_notify_merge_completed,
+                               pool);
+      n->kind = svn_node_none;
+      n->content_state = n->prop_state = svn_wc_notify_state_inapplicable;
+      n->lock_state = svn_wc_notify_lock_state_inapplicable;
+      n->revision = SVN_INVALID_REVNUM;
+      ctx->notify_func2(ctx->notify_baton2, n, pool);
+    }
+}
 
+/* Is the notification the result of a real operative merge? */
 #define IS_OPERATIVE_NOTIFICATION(notify)  \
                     (notify->content_state == svn_wc_notify_state_conflicted \
                      || notify->content_state == svn_wc_notify_state_merged  \
@@ -2693,13 +2803,26 @@ find_nearest_ancestor(const apr_array_he
                      || notify->action == svn_wc_notify_update_add \
                      || notify->action == svn_wc_notify_tree_conflict)
 
-/* Our svn_wc_notify_func2_t wrapper.*/
+/* Handle a diff notification by calling the client's notification callback
+ * and also by recording which paths changed (in BATON->*_abspaths).
+ *
+ * In some cases, notify that a merge is beginning, if we haven't already
+ * done so.  (### TODO: Harmonize this so it handles all cases.)
+ *
+ * The paths in NOTIFY are relpaths, relative to the root of the diff (the
+ * merge source). We convert these to abspaths in the merge target WC before
+ * passing the notification structure on to the client.
+ *
+ * This function is not used for 'starting a merge', 'starting to record
+ * mergeinfo' and 'completing a merge' notifications.
+ *
+ * Implements svn_wc_notify_func2_t.*/
 static void
 notification_receiver(void *baton, const svn_wc_notify_t *notify,
                       apr_pool_t *pool)
 {
   notification_receiver_baton_t *notify_b = baton;
-  svn_boolean_t is_operative_notification = FALSE;
+  svn_boolean_t is_operative_notification = IS_OPERATIVE_NOTIFICATION(notify);
   const char *notify_abspath;
 
   /* Skip notifications if this is a --record-only merge that is adding
@@ -2707,15 +2830,12 @@ notification_receiver(void *baton, const
      We will already have skipped the actual addition or deletion, but will
      still get a notification callback for it. */
   if (notify_b->merge_b->record_only
-      && (notify->action != svn_wc_notify_update_update
-          && notify->action != svn_wc_notify_merge_record_info_begin))
+      && notify->action != svn_wc_notify_update_update)
     return;
 
-  /* Is the notification the result of a real operative merge? */
-  if (IS_OPERATIVE_NOTIFICATION(notify))
+  if (is_operative_notification)
     {
       notify_b->nbr_operative_notifications++;
-      is_operative_notification = TRUE;
     }
 
   /* If the node was moved-away, use its new path in the notification. */
@@ -2752,6 +2872,7 @@ notification_receiver(void *baton, const
         notify_abspath = moved_to_abspath;
     }
 
+  /* Update the lists of merged, skipped, tree-conflicted and added paths. */
   if (notify_b->merge_b->sources_ancestral
       || notify_b->merge_b->reintegrate_merge)
     {
@@ -2813,6 +2934,9 @@ notification_receiver(void *baton, const
           else
             {
               added_path_parent = svn_dirent_dirname(added_path, pool);
+              /* ### Bug. Testing whether its immediate parent is in the
+               * hash isn't enough: this is letting every other level of
+               * the added subtree hierarchy into the hash. */
               if (!apr_hash_get(notify_b->added_abspaths, added_path_parent,
                                 APR_HASH_KEY_STRING))
                 is_root_of_added_subtree = TRUE;
@@ -2823,11 +2947,11 @@ notification_receiver(void *baton, const
         }
     }
 
+  /* Notify that a merge is beginning, if we haven't already done so.
+   * (A single-file merge is notified separately: see single_file_merge_notify().) */
   /* If our merge sources are ancestors of one another... */
   if (notify_b->merge_b->sources_ancestral)
     {
-      notify_b->nbr_notifications++;
-
       /* See if this is an operative directory merge. */
       if (!(notify_b->is_single_file_merge) && is_operative_notification)
         {
@@ -2846,36 +2970,22 @@ notification_receiver(void *baton, const
                --- Merging rX into 'PARENT'
                D    PARENT/CHILD
           */
-          int new_nearest_ancestor_index =
-            find_nearest_ancestor(
-              notify_b->children_with_mergeinfo,
-              notify->action != svn_wc_notify_update_delete,
-              notify_abspath);
-
-          if (new_nearest_ancestor_index != notify_b->cur_ancestor_index)
-            {
-              svn_client__merge_path_t *child =
-                APR_ARRAY_IDX(notify_b->children_with_mergeinfo,
-                              new_nearest_ancestor_index,
-                              svn_client__merge_path_t *);
-              notify_b->cur_ancestor_index = new_nearest_ancestor_index;
-              if (!child->absent && child->remaining_ranges->nelts > 0
-                  && !(new_nearest_ancestor_index == 0
-                       && child->remaining_ranges == 0))
+          const svn_client__merge_path_t *child
+            = find_nearest_ancestor(
+                notify_b->children_with_mergeinfo,
+                notify->action != svn_wc_notify_update_delete,
+                notify_abspath);
+
+          if (notify_b->cur_ancestor_abspath == NULL
+              || strcmp(child->abspath, notify_b->cur_ancestor_abspath) != 0)
+            {
+              notify_b->cur_ancestor_abspath = child->abspath;
+              if (!child->absent && child->remaining_ranges->nelts > 0)
                 {
-                  svn_wc_notify_t *notify_merge_begin;
-                  notify_merge_begin =
-                    svn_wc_create_notify(child->abspath,
-                                         notify_b->merge_b->same_repos
-                                           ? svn_wc_notify_merge_begin
-                                           : svn_wc_notify_foreign_merge_begin,
-                                         pool);
-                  notify_merge_begin->merge_range =
-                    APR_ARRAY_IDX(child->remaining_ranges, 0,
-                                  svn_merge_range_t *);
-                  if (notify_b->wrapped_func)
-                    (*notify_b->wrapped_func)(notify_b->wrapped_baton,
-                                              notify_merge_begin, pool);
+                  notify_merge_begin(child->abspath,
+                                     APR_ARRAY_IDX(child->remaining_ranges, 0,
+                                                   svn_merge_range_t *),
+                                     notify_b->merge_b, pool);
                 }
             }
         }
@@ -2885,16 +2995,8 @@ notification_receiver(void *baton, const
            && notify_b->nbr_operative_notifications == 1
            && is_operative_notification)
     {
-      svn_wc_notify_t *notify_merge_begin;
-      notify_merge_begin =
-        svn_wc_create_notify(notify_b->merge_b->target_abspath,
-                             notify_b->merge_b->same_repos
-                               ? svn_wc_notify_merge_begin
-                               : svn_wc_notify_foreign_merge_begin,
-                             pool);
-      if (notify_b->wrapped_func)
-        (*notify_b->wrapped_func)(notify_b->wrapped_baton, notify_merge_begin,
-                                  pool);
+      notify_merge_begin(notify_b->merge_b->target_abspath, NULL,
+                         notify_b->merge_b, pool);
     }
 
   if (notify_b->wrapped_func)
@@ -3043,22 +3145,14 @@ adjust_deleted_subtree_ranges(svn_client
   svn_revnum_t older_rev = is_rollback ? revision2 : revision1;
   apr_array_header_t *segments;
   const char *rel_source_path;
-  const char *session_url;
   svn_error_t *err;
 
   SVN_ERR_ASSERT(parent->remaining_ranges);
 
   /* We want to know about PRIMARY_URL@peg_rev, but we need PRIMARY_URL's
      path relative to RA_SESSION's URL. */
-  SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, scratch_pool));
-  SVN_ERR(svn_client__path_relative_to_root(&rel_source_path,
-                                            ctx->wc_ctx,
-                                            primary_url,
-                                            session_url,
-                                            FALSE,
-                                            ra_session,
-                                            scratch_pool,
-                                            scratch_pool));
+  SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &rel_source_path,
+                                              primary_url, scratch_pool));
   err = svn_client__repos_location_segments(&segments, ra_session,
                                             rel_source_path, peg_rev,
                                             younger_rev, older_rev, ctx,
@@ -3230,47 +3324,38 @@ adjust_deleted_subtree_ranges(svn_client
 
 /* Helper for do_directory_merge().
 
-   URL1, REVISION1, URL2, REVISION2, NOTIFY_B, and MERGE_B are
-   cascaded from the arguments of the same name in do_directory_merge().
-   RA_SESSION is the session for the younger of URL1@REVISION1 and
-   URL2@REVISION2.
+   SOURCE and MERGE_B are cascaded from the arguments of the same name in
+   do_directory_merge().  RA_SESSION is the session for the younger of
+   SOURCE->url1@rev1 and SOURCE->url2@rev2.
 
-   Adjust the subtrees in NOTIFY_B->CHILDREN_WITH_MERGEINFO so that we don't
+   Adjust the subtrees in CHILDREN_WITH_MERGEINFO so that we don't
    later try to describe invalid paths in drive_merge_report_editor().
    This function is just a thin wrapper around
    adjust_deleted_subtree_ranges(), which see for further details.
 
    SCRATCH_POOL is used for all temporary allocations.  Changes to
-   NOTIFY_B->CHILDREN_WITH_MERGEINFO are allocated in RESULT_POOL.
+   CHILDREN_WITH_MERGEINFO are allocated in RESULT_POOL.
 */
 static svn_error_t *
-fix_deleted_subtree_ranges(const char *url1,
-                           svn_revnum_t revision1,
-                           const char *url2,
-                           svn_revnum_t revision2,
+fix_deleted_subtree_ranges(const merge_source_t *source,
                            svn_ra_session_t *ra_session,
-                           notification_receiver_baton_t *notify_b,
+                           apr_array_header_t *children_with_mergeinfo,
                            merge_cmd_baton_t *merge_b,
                            apr_pool_t *result_pool,
                            apr_pool_t *scratch_pool)
 {
   int i;
-  const char *source_root_url;
   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
-  svn_boolean_t is_rollback = revision2 < revision1;
+  svn_boolean_t is_rollback = source->rev2 < source->rev1;
 
-  SVN_ERR(svn_ra_get_repos_root2(ra_session, &source_root_url, scratch_pool));
-
-  /* NOTIFY_B->CHILDREN_WITH_MERGEINFO is sorted in depth-first order, so
+  /* CHILDREN_WITH_MERGEINFO is sorted in depth-first order, so
      start at index 1 to examine only subtrees. */
-  for (i = 1; i < notify_b->children_with_mergeinfo->nelts; i++)
+  for (i = 1; i < children_with_mergeinfo->nelts; i++)
     {
       svn_client__merge_path_t *child =
-        APR_ARRAY_IDX(notify_b->children_with_mergeinfo, i,
-                      svn_client__merge_path_t *);
+        APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
       svn_client__merge_path_t *parent;
       apr_array_header_t *deleted_rangelist, *added_rangelist;
-      int parent_index;
 
       SVN_ERR_ASSERT(child);
       if (child->absent)
@@ -3279,10 +3364,8 @@ fix_deleted_subtree_ranges(const char *u
       svn_pool_clear(iterpool);
 
       /* Find CHILD's parent. */
-      parent_index = find_nearest_ancestor(notify_b->children_with_mergeinfo,
-                                           FALSE, child->abspath);
-      parent = APR_ARRAY_IDX(notify_b->children_with_mergeinfo, parent_index,
-                             svn_client__merge_path_t *);
+      parent = find_nearest_ancestor(children_with_mergeinfo,
+                                     FALSE, child->abspath);
 
       /* Since CHILD is a subtree then its parent must be in
          CHILDREN_WITH_MERGEINFO, see the global comment
@@ -3308,16 +3391,15 @@ fix_deleted_subtree_ranges(const char *u
           SVN_ERR(svn_rangelist_reverse(parent->remaining_ranges, iterpool));
         }
 
-      /* If CHILD is the merge target we then know that URL1, URL2,
-         REVISION1, and REVISION2 are provided by normalize_merge_sources()
-         -- see 'MERGEINFO MERGE SOURCE NORMALIZATION'.  Due to this
-         normalization we know that URL1@REVISION1 and URL2@REVISION2
-         describe an unbroken line of history such that the entire range
-         described by REVISION1:REVISION2 can potentially be merged to CHILD.
+      /* If CHILD is the merge target we then know that SOURCE is provided
+         by normalize_merge_sources() -- see 'MERGEINFO MERGE SOURCE
+         NORMALIZATION'.  Due to this normalization we know that SOURCE
+         describes an unbroken line of history such that the entire range
+         described by SOURCE can potentially be merged to CHILD.
 
          But if CHILD is a subtree we don't have the same guarantees about
-         URL1, URL2, REVISION1, and REVISION2 as we do for the merge target.
-         URL1@REVSION1 and/or URL2@REVSION2 might not exist.
+         SOURCE as we do for the merge target.  SOURCE->url1@rev1 and/or
+         SOURCE->url2@rev2 might not exist.
 
          If one or both doesn't exist, then adjust CHILD->REMAINING_RANGES
          such that we don't later try to describe invalid subtrees in
@@ -3339,18 +3421,17 @@ fix_deleted_subtree_ranges(const char *u
           SVN_ERR_ASSERT(child_repos_src_path);
 
           child_primary_source_url =
-            svn_path_url_add_component2((revision1 < revision2) ? url2 : url1,
+            svn_path_url_add_component2((source->rev1 < source->rev2)
+                                        ? source->url2 : source->url1,
                                         child_repos_src_path, iterpool);
 
-          SVN_ERR(svn_client__path_relative_to_root(&child_mergeinfo_path,
-                                                    merge_b->ctx->wc_ctx,
-                                                    child_primary_source_url,
-                                                    source_root_url,
-                                                    TRUE, ra_session,
-                                                    iterpool, iterpool));
+          SVN_ERR(svn_ra__get_fspath_relative_to_root(ra_session,
+                                                      &child_mergeinfo_path,
+                                                      child_primary_source_url,
+                                                      iterpool));
           SVN_ERR(adjust_deleted_subtree_ranges(child, parent,
                                                 child_mergeinfo_path,
-                                                revision1, revision2,
+                                                source->rev1, source->rev2,
                                                 child_primary_source_url,
                                                 ra_session,
                                                 merge_b->ctx, result_pool,
@@ -3409,22 +3490,18 @@ get_full_mergeinfo(svn_mergeinfo_t *reco
                    apr_pool_t *result_pool,
                    apr_pool_t *scratch_pool)
 {
-  svn_boolean_t inherited_mergeinfo = FALSE;
-
   /* First, we get the real mergeinfo. */
   if (recorded_mergeinfo)
     {
       svn_boolean_t inherited_from_repos;
 
       SVN_ERR(svn_client__get_wc_or_repos_mergeinfo(recorded_mergeinfo,
-                                                    &inherited_mergeinfo,
+                                                    inherited,
                                                     &inherited_from_repos,
                                                     FALSE,
                                                     inherit, ra_session,
                                                     target_abspath,
                                                     ctx, result_pool));
-      if (inherited)
-        *inherited = inherited_mergeinfo;
     }
 
   if (implicit_mergeinfo)
@@ -3509,7 +3586,7 @@ get_full_mergeinfo(svn_mergeinfo_t *reco
 
    Set CHILD->IMPLICIT_MERGEINFO to the mergeinfo inherited from
    PARENT->IMPLICIT_MERGEINFO.  CHILD->IMPLICIT_MERGEINFO is allocated
-   in POOL.
+   in RESULT_POOL.
    */
 static svn_error_t *
 inherit_implicit_mergeinfo_from_parent(svn_client__merge_path_t *parent,
@@ -3538,17 +3615,17 @@ inherit_implicit_mergeinfo_from_parent(s
                                ctx, result_pool, scratch_pool));
 
   /* Let CHILD inherit PARENT's implicit mergeinfo. */
-  child->implicit_mergeinfo = apr_hash_make(result_pool);
 
   path_diff = svn_dirent_is_child(parent->abspath, child->abspath,
                                   scratch_pool);
-
   /* PARENT->PATH better be an ancestor of CHILD->ABSPATH! */
   SVN_ERR_ASSERT(path_diff);
 
-  SVN_ERR(svn_client__adjust_mergeinfo_source_paths(
-    child->implicit_mergeinfo, path_diff,
-    parent->implicit_mergeinfo, result_pool));
+  SVN_ERR(svn_mergeinfo__add_suffix_to_mergeinfo(
+            &child->implicit_mergeinfo, parent->implicit_mergeinfo,
+            path_diff, result_pool, scratch_pool));
+  child->implicit_mergeinfo = svn_mergeinfo_dup(child->implicit_mergeinfo,
+                                                result_pool);
   return SVN_NO_ERROR;
 }
 
@@ -3561,7 +3638,7 @@ inherit_implicit_mergeinfo_from_parent(s
    SCRATCH_POOL for all temporary allocations.
 
    PARENT, CHILD, REVISION1, REVISION2, RA_SESSION, and
-   CTX are all cascased from the arguments of the same name in
+   CTX are all cascaded from the arguments of the same name in
    filter_merged_revisions() and the same conditions for that function
    hold here. */
 static svn_error_t *
@@ -3863,13 +3940,12 @@ filter_merged_revisions(svn_client__merg
 /* Helper for do_file_merge and do_directory_merge (by way of
    populate_remaining_ranges() for the latter).
 
-   Determine what portions of URL1@REVISION1 -> URL2@REVISION2 have already
+   Determine what portions of SOURCE have already
    been merged to CHILD->ABSPATH and populate CHILD->REMAINING_RANGES with
    the ranges that still need merging.
 
-   URL1, REVISION1, URL2, REVISION2, and CTX are all cascaded
-   from the caller's arguments of the same names.  Note that this means URL1,
-   REVISION1, URL2, and REVISION2 adhere to the requirements noted in
+   SOURCE and CTX are all cascaded from the caller's arguments of the same
+   names.  Note that this means SOURCE adheres to the requirements noted in
    `MERGEINFO MERGE SOURCE NORMALIZATION'.
 
    CHILD represents a working copy path which is the merge target or one of
@@ -3877,8 +3953,8 @@ filter_merged_revisions(svn_client__merg
    ancestor - see 'THE CHILDREN_WITH_MERGEINFO ARRAY'.  TARGET_MERGEINFO is
    the working mergeinfo on CHILD.
 
-   RA_SESSION is the session for, and SOURCE_ROOT_URL is the repository root
-   for, the younger of URL1@REVISION1 and URL2@REVISION2.
+   RA_SESSION is the session for the younger of SOURCE->url1@rev1 and
+   SOURCE->url2@rev2.
 
    If the function needs to consider CHILD->IMPLICIT_MERGEINFO and
    CHILD_INHERITS_IMPLICIT is true, then set CHILD->IMPLICIT_MERGEINFO to the
@@ -3886,7 +3962,7 @@ filter_merged_revisions(svn_client__merg
    the repository for CHILD->IMPLICIT_MERGEINFO.
 
    If not null, IMPLICIT_SRC_GAP is the gap, if any, in the natural history
-   of URL1@REVISION1:URL2@REVISION2, see merge_cmd_baton_t.implicit_src_gap.
+   of SOURCE, see merge_cmd_baton_t.implicit_src_gap.
 
    SCRATCH_POOL is used for all temporary allocations.  Changes to CHILD and
    PARENT are made in RESULT_POOL.
@@ -3899,19 +3975,15 @@ filter_merged_revisions(svn_client__merg
    in depth-first order.
 
    NOTE: When performing reverse merges, return
-   SVN_ERR_CLIENT_NOT_READY_TO_MERGE if URL1@REVISION1, URL2@REVISION2, and
+   SVN_ERR_CLIENT_NOT_READY_TO_MERGE if both locations in SOURCE and
    CHILD->ABSPATH are all on the same line of history but CHILD->ABSPATH's
-   base revision is older than the REVISION1-REVISION2 range, see comment re
+   base revision is older than the SOURCE->rev1:rev2 range, see comment re
    issue #2973 below.
 */
 static svn_error_t *
 calculate_remaining_ranges(svn_client__merge_path_t *parent,
                            svn_client__merge_path_t *child,
-                           const char *source_root_url,
-                           const char *url1,
-                           svn_revnum_t revision1,
-                           const char *url2,
-                           svn_revnum_t revision2,
+                           const merge_source_t *source,
                            svn_mergeinfo_t target_mergeinfo,
                            const apr_array_header_t *implicit_src_gap,
                            svn_boolean_t child_inherits_implicit,
@@ -3921,21 +3993,20 @@ calculate_remaining_ranges(svn_client__m
                            apr_pool_t *scratch_pool)
 {
   const char *mergeinfo_path;
-  const char *primary_url = (revision1 < revision2) ? url2 : url1;
+  const char *primary_url = (source->rev1 < source->rev2)
+                            ? source->url2 : source->url1;
   svn_mergeinfo_t adjusted_target_mergeinfo = NULL;
   svn_revnum_t child_base_revision;
 
   /* Determine which of the requested ranges to consider merging... */
-  SVN_ERR(svn_client__path_relative_to_root(&mergeinfo_path, ctx->wc_ctx,
-                                            primary_url, source_root_url, TRUE,
-                                            ra_session, result_pool,
-                                            scratch_pool));
+  SVN_ERR(svn_ra__get_fspath_relative_to_root(ra_session, &mergeinfo_path,
+                                              primary_url, result_pool));
 
   /* Consider: CHILD might have explicit mergeinfo '/MERGEINFO_PATH:M-N'
-     where M-N fall into the gap in URL1@REVISION1:URL2@REVISION2's natural
+     where M-N fall into the gap in SOURCE's natural
      history allowed by 'MERGEINFO MERGE SOURCE NORMALIZATION'.  If this is
      the case, then '/MERGEINFO_PATH:N' actually refers to a completely
-     different line of history than URL1@REVISION1:URL2@REVISION2 and we
+     different line of history than SOURCE and we
      *don't* want to consider those revisions merged already. */
   if (implicit_src_gap && child->pre_merge_mergeinfo)
     {
@@ -3963,7 +4034,7 @@ calculate_remaining_ranges(svn_client__m
      merged (or, in the case of reverse merges, ranges not yet merged). */
   SVN_ERR(filter_merged_revisions(parent, child, mergeinfo_path,
                                   adjusted_target_mergeinfo,
-                                  revision1, revision2,
+                                  source->rev1, source->rev2,
                                   child_inherits_implicit,
                                   ra_session, ctx, result_pool,
                                   scratch_pool));
@@ -4000,23 +4071,18 @@ calculate_remaining_ranges(svn_client__m
      can't have any "future" history. */
   if (SVN_IS_VALID_REVNUM(child_base_revision)
       && ((child->remaining_ranges)->nelts == 0) /* Inoperative merge */
-      && (revision2 < revision1)                 /* Reverse merge */
-      && (child_base_revision <= revision2))     /* From CHILD's future */
+      && (source->rev2 < source->rev1)                 /* Reverse merge */
+      && (child_base_revision <= source->rev2))     /* From CHILD's future */
     {
       /* Hmmm, an inoperative reverse merge from the "future".  If it is
          from our own future return a helpful error. */
       svn_error_t *err;
       const char *start_url;
-      svn_opt_revision_t requested, pegrev, *start_revision;
-      requested.kind = svn_opt_revision_number;
-      requested.value.number = child_base_revision;
-      pegrev.kind = svn_opt_revision_number;
-      pegrev.value.number = revision1;
-
-      err = svn_client__repos_locations(&start_url, &start_revision,
-                                        NULL, NULL, ra_session, url1,
-                                        &pegrev, &requested,
-                                        NULL, ctx, scratch_pool);
+
+      err = svn_client__repos_location(&start_url,
+                                       ra_session, source->url1, source->rev1,
+                                       child_base_revision,
+                                       ctx, scratch_pool, scratch_pool);
       if (err)
         {
           if (err->apr_err == SVN_ERR_FS_NOT_FOUND
@@ -4044,24 +4110,23 @@ calculate_remaining_ranges(svn_client__m
 
 /* Helper for populate_remaining_ranges().
 
-   URL1, REVISION1, URL2, REVISION2, RA_SESSION, MERGE_SRC_CANON_PATH,
+   SOURCE, RA_SESSION, MERGE_SRC_CANON_PATH,
    and MERGE_B are all cascaded from the arguments of the same name in
    populate_remaining_ranges().  MERGE_SRC_CANON_PATH is the absolute
-   repository path of URL2.
+   repository path of SOURCE->url2.
 
-   Note: The following comments assume a forward merge, i.e.
-   REVISION1 < REVISION2.  If this is a reverse merge then all the following
-   comments still apply, but with URL1 switched with URL2 and REVISION1
-   switched with REVISION2.
-
-   Like populate_remaining_ranges(), URL1@REVISION1:URL2@REVISION2 must adhere
-   to the restrictions documented in 'MERGEINFO MERGE SOURCE NORMALIZATION'.
-   These restrictions allow for a *single* gap, URL@GAP_REV1:URL2@GAP_REV2,
-   (where REVISION1 < GAP_REV1 <= GAP_REV2 < REVISION2) in
-   URL1@REVISION1:URL2@REVISION2 if URL2@REVISION2 was copied from
-   URL1@REVISION1.  If such a gap exists, set *GAP_START and *GAP_END to the
-   starting and ending revisions of the gap.  Otherwise set both to
-   SVN_INVALID_REVNUM.
+   Note: The following comments assume a forward merge, i.e. SOURCE->rev1
+   < SOURCE->rev2.  If this is a reverse merge then all the following
+   comments still apply, but with SOURCE->url1 switched with SOURCE->url2
+   and SOURCE->rev1 switched with SOURCE->rev2.
+
+   Like populate_remaining_ranges(), SOURCE must adhere to the restrictions
+   documented in 'MERGEINFO MERGE SOURCE NORMALIZATION'.  These restrictions
+   allow for a *single* gap, URL@GAP_REV1:URL2@GAP_REV2, (where SOURCE->rev1
+   < GAP_REV1 <= GAP_REV2 < SOURCE->rev2) in SOURCE if SOURCE->url2@rev2 was
+   copied from SOURCE->url1@rev1.  If such a gap exists, set *GAP_START and
+   *GAP_END to the starting and ending revisions of the gap.  Otherwise set
+   both to SVN_INVALID_REVNUM.
 
    For example, if the natural history of URL@2:URL@9 is 'trunk/:2,7-9' this
    would indicate that trunk@7 was copied from trunk@2.  This function would
@@ -4072,23 +4137,20 @@ static svn_error_t *
 find_gaps_in_merge_source_history(svn_revnum_t *gap_start,
                                   svn_revnum_t *gap_end,
                                   const char *merge_src_canon_path,
-                                  const char *url1,
-                                  svn_revnum_t revision1,
-                                  const char *url2,
-                                  svn_revnum_t revision2,
+                                  const merge_source_t *source,
                                   svn_ra_session_t *ra_session,
                                   merge_cmd_baton_t *merge_b,
                                   apr_pool_t *scratch_pool)
 {
   svn_mergeinfo_t implicit_src_mergeinfo;
-  svn_revnum_t young_rev = MAX(revision1, revision2);
-  svn_revnum_t old_rev = MIN(revision1, revision2);
+  svn_revnum_t young_rev = MAX(source->rev1, source->rev2);
+  svn_revnum_t old_rev = MIN(source->rev1, source->rev2);
   apr_array_header_t *rangelist;
 
   /* Start by assuming there is no gap. */
   *gap_start = *gap_end = SVN_INVALID_REVNUM;
 
-  /* Get URL1@REVISION1:URL2@REVISION2 as mergeinfo. */
+  /* Get SOURCE as mergeinfo. */
   SVN_ERR(svn_client__get_history_as_mergeinfo(&implicit_src_mergeinfo, NULL,
                                                young_rev, young_rev,
                                                old_rev, ra_session,
@@ -4128,7 +4190,7 @@ find_gaps_in_merge_source_history(svn_re
       /* As mentioned above, multiple gaps *shouldn't* be possible. */
       SVN_ERR_ASSERT(apr_hash_count(implicit_src_mergeinfo) == 1);
 
-      *gap_start = MIN(revision1, revision2);
+      *gap_start = MIN(source->rev1, source->rev2);
       *gap_end = (APR_ARRAY_IDX(rangelist,
                                 rangelist->nelts - 1,
                                 svn_merge_range_t *))->start;
@@ -4136,8 +4198,8 @@ find_gaps_in_merge_source_history(svn_re
   else if (apr_hash_count(implicit_src_mergeinfo) > 1) /* Rename */
     {
       apr_array_header_t *requested_rangelist =
-        svn_rangelist__initialize(MIN(revision1, revision2),
-                                  MAX(revision1, revision2),
+        svn_rangelist__initialize(MIN(source->rev1, source->rev2),
+                                  MAX(source->rev1, source->rev2),
                                   TRUE, scratch_pool);
       apr_array_header_t *implicit_rangelist =
         apr_array_make(scratch_pool, 2, sizeof(svn_merge_range_t *));
@@ -4174,25 +4236,21 @@ find_gaps_in_merge_source_history(svn_re
    If HONOR_MERGEINFO is set, this function will actually try to be
    intelligent about populating remaining_ranges list.  Otherwise, it
    will claim that each child has a single remaining range, from
-   revision1, to revision2.
+   SOURCE->rev1, to SOURCE->rev2.
 
    SCRATCH_POOL is used for all temporary allocations.  Changes to
    CHILDREN_WITH_MERGEINFO are made in RESULT_POOL.
 
-   Note that if REVISION1 > REVISION2, then each child's remaining_ranges
+   Note that if SOURCE->rev1 > SOURCE->rev2, then each child's remaining_ranges
    member does not adhere to the API rules for rangelists described in
    svn_mergeinfo.h -- See svn_client__merge_path_t.
 
    See `MERGEINFO MERGE SOURCE NORMALIZATION' for more requirements
-   around the values of URL1, REVISION1, URL2, and REVISION2.
+   around SOURCE.
 */
 static svn_error_t *
 populate_remaining_ranges(apr_array_header_t *children_with_mergeinfo,
-                          const char *source_root_url,
-                          const char *url1,
-                          svn_revnum_t revision1,
-                          const char *url2,
-                          svn_revnum_t revision2,
+                          const merge_source_t *source,
                           svn_boolean_t honor_mergeinfo,
                           svn_ra_session_t *ra_session,
                           const char *parent_merge_src_canon_path,
@@ -4203,12 +4261,9 @@ populate_remaining_ranges(apr_array_head
   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
   int i;
   svn_revnum_t gap_start, gap_end;
-  svn_boolean_t child_inherits_implicit;
-  svn_client__merge_path_t *parent;
-  int parent_index;
 
   /* If we aren't honoring mergeinfo or this is a --record-only merge,
-     we'll make quick work of this by simply adding dummy REVISION1:REVISION2
+     we'll make quick work of this by simply adding dummy SOURCE->rev1:rev2
      ranges for all children. */
   if (! honor_mergeinfo || merge_b->record_only)
     {
@@ -4218,7 +4273,6 @@ populate_remaining_ranges(apr_array_head
             APR_ARRAY_IDX(children_with_mergeinfo, i,
                           svn_client__merge_path_t *);
 
-          parent = NULL;
           svn_pool_clear(iterpool);
 
           /* Issue #3646 'record-only merges create self-referential
@@ -4232,8 +4286,8 @@ populate_remaining_ranges(apr_array_head
                                          NULL, /* child->inherited_mergeinfo */
                                          svn_mergeinfo_inherited, ra_session,
                                          child->abspath,
-                                         MAX(revision1, revision2),
-                                         MIN(revision1, revision2),
+                                         MAX(source->rev1, source->rev2),
+                                         MIN(source->rev1, source->rev2),
                                          merge_b->ctx, result_pool,
                                          iterpool));
             }
@@ -4241,10 +4295,11 @@ populate_remaining_ranges(apr_array_head
             {
               /* Issue #3443 - Subtrees of the merge target can inherit
                  their parent's implicit mergeinfo in most cases. */
-              parent_index = find_nearest_ancestor(children_with_mergeinfo,
-                                                   FALSE, child->abspath);
-              parent = APR_ARRAY_IDX(children_with_mergeinfo, parent_index,
-                                     svn_client__merge_path_t *);
+              svn_client__merge_path_t *parent
+                = find_nearest_ancestor(children_with_mergeinfo,
+                                        FALSE, child->abspath);
+              svn_boolean_t child_inherits_implicit;
+
               /* If CHILD is a subtree then its parent must be in
                  CHILDREN_WITH_MERGEINFO, see the global comment
                  'THE CHILDREN_WITH_MERGEINFO ARRAY'. */
@@ -4253,13 +4308,13 @@ populate_remaining_ranges(apr_array_head
               child_inherits_implicit = (parent && !child->switched);
               SVN_ERR(ensure_implicit_mergeinfo(parent, child,
                                                 child_inherits_implicit,
-                                                revision1, revision2,
+                                                source->rev1, source->rev2,
                                                 ra_session, merge_b->ctx,
                                                 result_pool, iterpool));
             }
 
-          child->remaining_ranges = svn_rangelist__initialize(revision1,
-                                                              revision2,
+          child->remaining_ranges = svn_rangelist__initialize(source->rev1,
+                                                              source->rev2,
                                                               TRUE,
                                                               result_pool);
         }
@@ -4268,21 +4323,20 @@ populate_remaining_ranges(apr_array_head
     }
 
   /* If, in the merge source's history, there was a copy from a older
-     revision, then URL2 won't exist at some range M:N, where
-     REVISION1 < M < N < REVISION2. The rules of 'MERGEINFO MERGE SOURCE
-     NORMALIZATION' allow this, but we must ignore these gaps when
-     calculating what ranges remain to be merged from
-     URL1@REVISION1:URL2@REVISION2.  If we don't and try to merge any part
-     of URL2@M:URL2@N we would break the editor since no part of that
-     actually exists.  See http://svn.haxx.se/dev/archive-2008-11/0618.shtml.
+     revision, then SOURCE->url2 won't exist at some range M:N, where
+     source->rev1 < M < N < source->rev2. The rules of 'MERGEINFO MERGE
+     SOURCE NORMALIZATION' allow this, but we must ignore these gaps when
+     calculating what ranges remain to be merged from SOURCE. If we don't
+     and try to merge any part of SOURCE->url2@M:N we would break the
+     editor since no part of that actually exists.  See
+     http://svn.haxx.se/dev/archive-2008-11/0618.shtml.
 
      Find the gaps in the merge target's history, if any.  Eventually
      we will adjust CHILD->REMAINING_RANGES such that we don't describe
      non-existent paths to the editor. */
   SVN_ERR(find_gaps_in_merge_source_history(&gap_start, &gap_end,
                                             parent_merge_src_canon_path,
-                                            url1, revision1,
-                                            url2, revision2,
+                                            source,
                                             ra_session, merge_b,
                                             iterpool));
 
@@ -4295,11 +4349,11 @@ populate_remaining_ranges(apr_array_head
   for (i = 0; i < children_with_mergeinfo->nelts; i++)
     {
       const char *child_repos_path;
-      const char *child_url1, *child_url2;
+      merge_source_t child_source = *source;
       svn_client__merge_path_t *child =
         APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
-
-      parent = NULL;
+      svn_client__merge_path_t *parent = NULL;
+      svn_boolean_t child_inherits_implicit;
 
       svn_pool_clear(iterpool);
 
@@ -4310,15 +4364,13 @@ populate_remaining_ranges(apr_array_head
 
       svn_pool_clear(iterpool);
 
-      child_repos_path = svn_dirent_is_child(merge_b->target_abspath,
-                                             child->abspath, iterpool);
-      if (!child_repos_path)
-        child_repos_path = "";
-
-      child_url1 = svn_path_url_add_component2(url1, child_repos_path,
-                                               iterpool);
-      child_url2 = svn_path_url_add_component2(url2, child_repos_path,
-                                               iterpool);
+      child_repos_path = svn_dirent_skip_ancestor(merge_b->target_abspath,
+                                                  child->abspath);
+      SVN_ERR_ASSERT(child_repos_path != NULL);
+      child_source.url1 = svn_path_url_add_component2(source->url1, child_repos_path,
+                                                      iterpool);
+      child_source.url2 = svn_path_url_add_component2(source->url2, child_repos_path,
+                                                      iterpool);
 
       /* Get the explicit/inherited mergeinfo for CHILD.  If CHILD is the
          merge target then also get its implicit mergeinfo.  Otherwise defer
@@ -4331,17 +4383,15 @@ populate_remaining_ranges(apr_array_head
         &(child->inherited_mergeinfo),
         svn_mergeinfo_inherited, ra_session,
         child->abspath,
-        MAX(revision1, revision2),
-        MIN(revision1, revision2),
+        MAX(source->rev1, source->rev2),
+        MIN(source->rev1, source->rev2),
         merge_b->ctx, result_pool, iterpool));
 
       /* If CHILD isn't the merge target find its parent. */
       if (i > 0)
         {
-          parent_index = find_nearest_ancestor(children_with_mergeinfo,
-                                               FALSE, child->abspath);
-          parent = APR_ARRAY_IDX(children_with_mergeinfo, parent_index,
-                                 svn_client__merge_path_t *);
+          parent = find_nearest_ancestor(children_with_mergeinfo,
+                                         FALSE, child->abspath);
           /* If CHILD is a subtree then its parent must be in
              CHILDREN_WITH_MERGEINFO, see the global comment
              'THE CHILDREN_WITH_MERGEINFO ARRAY'. */
@@ -4356,9 +4406,7 @@ populate_remaining_ranges(apr_array_head
       child_inherits_implicit = (parent && !child->switched);
 
       SVN_ERR(calculate_remaining_ranges(parent, child,
-                                         source_root_url,
-                                         child_url1, revision1,
-                                         child_url2, revision2,
+                                         &child_source,
                                          child->pre_merge_mergeinfo,
                                          merge_b->implicit_src_gap,
                                          child_inherits_implicit,
@@ -4366,7 +4414,7 @@ populate_remaining_ranges(apr_array_head
                                          merge_b->ctx, result_pool,
                                          iterpool));
 
-      /* Deal with any gap in URL1@REVISION1:URL2@REVISION2's natural history.
+      /* Deal with any gap in SOURCE's natural history.
 
          If the gap is a proper subset of CHILD->REMAINING_RANGES then we can
          safely ignore it since we won't describe this path/rev pair.
@@ -4382,32 +4430,31 @@ populate_remaining_ranges(apr_array_head
           && merge_b->implicit_src_gap)
         {
           int j;
-          svn_revnum_t start, end;
           svn_boolean_t proper_subset = FALSE;
           svn_boolean_t overlaps_or_adjoins = FALSE;
 
           /* If this is a reverse merge reorder CHILD->REMAINING_RANGES
               so it will work with the svn_rangelist_* APIs below. */
-          if (revision1 > revision2)
+          if (source->rev1 > source->rev2)
             SVN_ERR(svn_rangelist_reverse(child->remaining_ranges, iterpool));
 
           for (j = 0; j < child->remaining_ranges->nelts; j++)
             {
-              start = (APR_ARRAY_IDX(child->remaining_ranges, j,
-                                     svn_merge_range_t *))->start;
-              end = (APR_ARRAY_IDX(child->remaining_ranges, j,
-                                   svn_merge_range_t *))->end;
-              if ((start <= gap_start && gap_end < end)
-                  || (start < gap_start && gap_end <= end))
+              svn_merge_range_t *range
+                = APR_ARRAY_IDX(child->remaining_ranges, j, svn_merge_range_t *);
+
+              if ((range->start <= gap_start && gap_end < range->end)
+                  || (range->start < gap_start && gap_end <= range->end))
                 {
                   proper_subset = TRUE;
                   break;
                 }
-              else if ((gap_start == start) && (end == gap_end))
+              else if ((gap_start == range->start) && (range->end == gap_end))
                 {
                   break;
                 }
-              else if (gap_start <= end && start <= gap_end)  /* intersect */
+              else if (gap_start <= range->end && range->start <= gap_end)
+                /* intersect */
                 {
                   overlaps_or_adjoins = TRUE;
                   break;
@@ -4430,7 +4477,7 @@ populate_remaining_ranges(apr_array_head
                                              result_pool));
             }
 
-          if (revision1 > revision2) /* Reverse merge */
+          if (source->rev1 > source->rev2) /* Reverse merge */
             SVN_ERR(svn_rangelist_reverse(child->remaining_ranges, iterpool));
         }
     }
@@ -4444,79 +4491,9 @@ populate_remaining_ranges(apr_array_head
 
 /*** Other Helper Functions ***/
 
-/* Helper for record_mergeinfo_for_dir_merge().
-
-   Adjust, in place, the inheritability of the ranges in RANGELIST to
-   describe a merge of RANGELIST into WC_WCPATH at depth DEPTH.  Set
-   *RANGELIST_INHERITANCE to the inheritability set.
-
-   WC_PATH_IS_MERGE_TARGET is true if WC_PATH is the target of the merge,
-   otherwise WC_PATH is a subtree.
-
-   WC_PATH_HAS_MISSING_CHILD is true if WC_PATH is missing an immediate child
-   because the child is switched or absent from the WC, or due to a sparse
-   checkout -- see get_mergeinfo_paths().
-
-   Perform any temporary allocations in SCRATCH_POOL. */
-static svn_error_t *
-calculate_merge_inheritance(apr_array_header_t *rangelist,
-                            svn_boolean_t *rangelist_inheritance,
-                            const char *local_abspath,
-                            svn_boolean_t wc_path_is_merge_target,
-                            svn_boolean_t wc_path_has_missing_child,
-                            svn_depth_t depth,
-                            svn_wc_context_t *wc_ctx,
-                            apr_pool_t * scratch_pool)
-{
-  svn_node_kind_t path_kind;
-
-  SVN_ERR(svn_wc_read_kind(&path_kind, wc_ctx, local_abspath, FALSE,
-                           scratch_pool));
-
-  /* Starting assumption. */
-  *rangelist_inheritance = TRUE;
-
-  if (path_kind == svn_node_file)
-    {
-      /* Files *never* have non-inheritable mergeinfo. */
-      svn_rangelist__set_inheritance(rangelist, TRUE);
-    }
-  else if (path_kind == svn_node_dir)
-    {
-      if (wc_path_is_merge_target)
-        {
-          if (wc_path_has_missing_child
-              || depth == svn_depth_files
-              || depth == svn_depth_empty)
-            {
-              svn_rangelist__set_inheritance(rangelist, FALSE);
-              *rangelist_inheritance = FALSE;
-            }
-          else /* depth == svn_depth_files || depth == svn_depth_empty */
-            {
-              svn_rangelist__set_inheritance(rangelist, TRUE);
-            }
-        }
-      else /* WC_PATH is a directory subtree of the target. */
-        {
-          if (wc_path_has_missing_child
-              || depth == svn_depth_immediates)
-            {
-              svn_rangelist__set_inheritance(rangelist, FALSE);
-              *rangelist_inheritance = FALSE;
-            }
-          else /* depth == infinity */
-            {
-              svn_rangelist__set_inheritance(rangelist, TRUE);
-            }
-        }
-    }
-  return SVN_NO_ERROR;
-}
-
 /* Calculate the new mergeinfo for the target tree rooted at TARGET_ABSPATH
    based on MERGES (a mapping of absolute WC paths to rangelists representing
-   a merge from the source REPOS_REL_PATH).
+   a merge from the source SOURCE_FSPATH).
 
    If RESULT_CATALOG is NULL, then record the new mergeinfo in the WC (at,
    and possibly below, TARGET_ABSPATH).
@@ -4529,15 +4506,13 @@ calculate_merge_inheritance(apr_array_he
 static svn_error_t *
 update_wc_mergeinfo(svn_mergeinfo_catalog_t result_catalog,
                     const char *target_abspath,
-                    const char *repos_rel_path,
+                    const char *source_fspath,
                     apr_hash_t *merges,
                     svn_boolean_t is_rollback,
                     svn_client_ctx_t *ctx,
                     apr_pool_t *scratch_pool)
 {
   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
-  const char *rel_path;
-  svn_mergeinfo_t mergeinfo;
   apr_hash_index_t *hi;
 
   /* Combine the mergeinfo for the revision range just merged into
@@ -4549,6 +4524,8 @@ update_wc_mergeinfo(svn_mergeinfo_catalo
       apr_array_header_t *rangelist;
       svn_error_t *err;
       const char *local_abspath_rel_to_target;
+      const char *fspath;
+      svn_mergeinfo_t mergeinfo;
 
       svn_pool_clear(iterpool);
 
@@ -4579,8 +4556,7 @@ update_wc_mergeinfo(svn_mergeinfo_catalo
          mergeinfo that path inherits. */
       if (mergeinfo == NULL && ranges->nelts == 0)
         {
-          svn_boolean_t inherited;
-          SVN_ERR(svn_client__get_wc_mergeinfo(&mergeinfo, &inherited,
+          SVN_ERR(svn_client__get_wc_mergeinfo(&mergeinfo, NULL,
                                                svn_mergeinfo_nearest_ancestor,
                                                local_abspath, NULL, NULL,
                                                FALSE, ctx, iterpool, iterpool));
@@ -4589,16 +4565,13 @@ update_wc_mergeinfo(svn_mergeinfo_catalo
       if (mergeinfo == NULL)
         mergeinfo = apr_hash_make(iterpool);
 
-      local_abspath_rel_to_target = svn_dirent_is_child(target_abspath,
-                                                        local_abspath,
-                                                        iterpool);
-      if (local_abspath_rel_to_target)
-        rel_path = svn_dirent_join(repos_rel_path,
-                                   local_abspath_rel_to_target,
-                                   iterpool);
-      else
-          rel_path = repos_rel_path;
-      rangelist = apr_hash_get(mergeinfo, rel_path, APR_HASH_KEY_STRING);
+      local_abspath_rel_to_target = svn_dirent_skip_ancestor(target_abspath,
+                                                             local_abspath);
+      SVN_ERR_ASSERT(local_abspath_rel_to_target != NULL);
+      fspath = svn_fspath__join(source_fspath,
+                                local_abspath_rel_to_target,
+                                iterpool);
+      rangelist = apr_hash_get(mergeinfo, fspath, APR_HASH_KEY_STRING);
       if (rangelist == NULL)
         rangelist = apr_array_make(iterpool, 0, sizeof(svn_merge_range_t *));
 
@@ -4615,7 +4588,7 @@ update_wc_mergeinfo(svn_mergeinfo_catalo
           SVN_ERR(svn_rangelist_merge2(rangelist, ranges, iterpool, iterpool));
         }
       /* Update the mergeinfo by adjusting the path's rangelist. */
-      apr_hash_set(mergeinfo, rel_path, APR_HASH_KEY_STRING, rangelist);
+      apr_hash_set(mergeinfo, fspath, APR_HASH_KEY_STRING, rangelist);
 
       if (is_rollback && apr_hash_count(mergeinfo) == 0)
         mergeinfo = NULL;
@@ -4668,11 +4641,11 @@ update_wc_mergeinfo(svn_mergeinfo_catalo
 
    Record override mergeinfo on any paths skipped during a merge.
 
-   Set empty mergeinfo on each path in NOTIFY_B->SKIPPED_PATHS so the path
+   Set empty mergeinfo on each path in SKIPPED_ABSPATHS so the path
    does not incorrectly inherit mergeinfo that will later be describing
    the merge.
 
-   MERGEINFO_PATH, NOTIFY_B, and MERGE_B are all cascased from
+   MERGEINFO_PATH and MERGE_B are cascaded from
    arguments of the same name in the caller.
 
    IS_ROLLBACK is true if the caller is recording a reverse merge and false
@@ -4682,14 +4655,14 @@ static svn_error_t *
 record_skips(const char *mergeinfo_path,
              const apr_array_header_t *rangelist,
              svn_boolean_t is_rollback,
-             notification_receiver_baton_t *notify_b,
+             apr_hash_t *skipped_abspaths,
              merge_cmd_baton_t *merge_b,
              apr_pool_t *scratch_pool)
 {
   apr_hash_index_t *hi;
   apr_hash_t *merges;
-  apr_size_t nbr_skips = (notify_b->skipped_abspaths != NULL ?
-                          apr_hash_count(notify_b->skipped_abspaths) : 0);
+  apr_size_t nbr_skips = (skipped_abspaths != NULL ?
+                          apr_hash_count(skipped_abspaths) : 0);
   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
 
   if (nbr_skips == 0)
@@ -4698,7 +4671,7 @@ record_skips(const char *mergeinfo_path,
   merges = apr_hash_make(scratch_pool);
 
   /* Override the mergeinfo for child paths which weren't actually merged. */
-  for (hi = apr_hash_first(scratch_pool, notify_b->skipped_abspaths); hi;
+  for (hi = apr_hash_first(scratch_pool, skipped_abspaths); hi;
        hi = apr_hash_next(hi))
     {
       const char *skipped_abspath = svn__apr_hash_index_key(hi);
@@ -4732,11 +4705,10 @@ record_skips(const char *mergeinfo_path,
                    apr_array_make(scratch_pool, 0,
                                   sizeof(svn_merge_range_t *)));
 
-      if (nbr_skips < notify_b->nbr_notifications)
-        /* ### Use RANGELIST as the mergeinfo for all children of
+      /* if (nbr_skips < notify_b->nbr_notifications)
+           ### Use RANGELIST as the mergeinfo for all children of
            ### this path which were not also explicitly
            ### skipped? */
-        ;
     }
   SVN_ERR(update_wc_mergeinfo(NULL, merge_b->target_abspath,
                               mergeinfo_path, merges,
@@ -4771,8 +4743,7 @@ make_merge_conflict_error(const char *ta
    or are descendants of TARGET_WCPATH by setting those children to NULL. */
 static void
 remove_absent_children(const char *target_wcpath,
-                       apr_array_header_t *children_with_mergeinfo,
-                       notification_receiver_baton_t *notify_b)
+                       apr_array_header_t *children_with_mergeinfo)
 {
   /* Before we try to override mergeinfo for skipped paths, make sure
      the path isn't absent due to authz restrictions, because there's
@@ -4781,8 +4752,7 @@ remove_absent_children(const char *targe
   for (i = 0; i < children_with_mergeinfo->nelts; i++)
     {
       svn_client__merge_path_t *child =
-        APR_ARRAY_IDX(children_with_mergeinfo,
-                      i, svn_client__merge_path_t *);
+        APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
       if ((child->absent || child->scheduled_for_deletion)
           && svn_dirent_is_ancestor(target_wcpath, child->abspath))
         {
@@ -4791,51 +4761,48 @@ remove_absent_children(const char *targe
     }
 }
 
-/* Helper for do_directory_merge() to handle the case were a merge editor
+/* Helper for do_directory_merge() to handle the case where a merge editor
    drive removes explicit mergeinfo from a subtree of the merge target.
 
-   MERGE_B, NOTIFY_B are cascaded from the arguments of the same name in
+   MERGE_B is cascaded from the argument of the same name in
    do_directory_merge().  If MERGE_B->DRY_RUN is true do nothing, if it is
    false then for each path (if any) in MERGE_B->PATHS_WITH_DELETED_MERGEINFO
-   remove that path from NOTIFY_B->CHILDREN_WITH_MERGEINFO by setting that
+   remove that path from CHILDREN_WITH_MERGEINFO by setting that
    child to NULL.  The one exception is for the merge target itself,
    MERGE_B->TARGET_ABSPATH, this must always be present in
-   NOTIFY_B->CHILDREN_WITH_MERGEINFO so this is never removed by this
+   CHILDREN_WITH_MERGEINFO so this is never removed by this
    function. */
 static void
 remove_children_with_deleted_mergeinfo(merge_cmd_baton_t *merge_b,
-                                       notification_receiver_baton_t *notify_b)
+                                       apr_array_header_t *children_with_mergeinfo)
 {
   int i;
 
   if (merge_b->dry_run || !merge_b->paths_with_deleted_mergeinfo)
     return;
 
-  /* NOTIFY_B->CHILDREN_WITH_MERGEINFO[0] is the always the merge target
+  /* CHILDREN_WITH_MERGEINFO[0] is the always the merge target
      so start at the first child. */
-  for (i = 1; i < notify_b->children_with_mergeinfo->nelts; i++)
+  for (i = 1; i < children_with_mergeinfo->nelts; i++)
     {
       svn_client__merge_path_t *child =
-        APR_ARRAY_IDX(notify_b->children_with_mergeinfo,
-                      i, svn_client__merge_path_t *);
+        APR_ARRAY_IDX(children_with_mergeinfo, i, svn_client__merge_path_t *);
       if (apr_hash_get(merge_b->paths_with_deleted_mergeinfo,
-                       child->abspath,
-                       APR_HASH_KEY_STRING))
+                       child->abspath, APR_HASH_KEY_STRING))
         {
-          svn_sort__array_delete(notify_b->children_with_mergeinfo, i--, 1);
+          svn_sort__array_delete(children_with_mergeinfo, i--, 1);
         }
     }
 }
 
 /* Helper for do_directory_merge().
 
-   Set up the diff editor report to merge URL1@REVISION1 to URL2@REVISION2
+   Set up the diff editor report to merge the SOURCE diff
    into TARGET_ABSPATH and drive it.
 
    If mergeinfo is not being honored based on MERGE_B, see the doc string for
    HONOR_MERGEINFO() for how this is determined, then ignore
-   CHILDREN_WITH_MERGEINFO and merge the diff between URL1@REVISION1 and
-   URL2@REVISION2 to TARGET_ABSPATH.
+   CHILDREN_WITH_MERGEINFO and merge the SOURCE diff to TARGET_ABSPATH.
 
    If mergeinfo is being honored then perform a history-aware merge,
    describing TARGET_ABSPATH and its subtrees to the reporter in such as way
@@ -4847,36 +4814,36 @@ remove_children_with_deleted_mergeinfo(m
    svn_client__merge_path_t * -- see 'THE CHILDREN_WITH_MERGEINFO ARRAY'
    comment at the top of this file for more info.  Note that it is possible
    TARGET_ABSPATH and/or some of its subtrees need only a subset, or no part,
-   of REVISION1:REVISION2 to be merged.  Though there is little point to
+   of SOURCE to be merged.  Though there is little point to
    calling this function if TARGET_ABSPATH and all its subtrees have already
-   had URL1@REVISION1 to URL2@REVISION2 merged, this will work but is a no-op.
+   had SOURCE merged, this will work but is a no-op.
 
-   REVISION1 and REVISION2 must be bound by the set of remaining_ranges
+   SOURCE->rev1 and SOURCE->rev2 must be bound by the set of remaining_ranges
    fields in CHILDREN_WITH_MERGEINFO's elements, specifically:
 
-   For forward merges (REVISION1 < REVISION2):
+   For forward merges (SOURCE->rev1 < SOURCE->rev2):
 
      1) The first svn_merge_range_t * element of each child's remaining_ranges
         array must meet one of the following conditions:
 
-        a) The range's start field is greater than or equal to REVISION2.
+        a) The range's start field is greater than or equal to SOURCE->rev2.
 
-        b) The range's end field is REVISION2.
+        b) The range's end field is SOURCE->rev2.
 
      2) Among all the ranges that meet condition 'b' the oldest start
-        revision must equal REVISION1.
+        revision must equal SOURCE->rev1.
 
-   For reverse merges (REVISION1 > REVISION2):
+   For reverse merges (SOURCE->rev1 > SOURCE->rev2):
 
      1) The first svn_merge_range_t * element of each child's remaining_ranges
         array must meet one of the following conditions:
 
-        a) The range's start field is less than or equal to REVISION2.
+        a) The range's start field is less than or equal to SOURCE->rev2.
 
-        b) The range's end field is REVISION2.
+        b) The range's end field is SOURCE->rev2.
 
      2) Among all the ranges that meet condition 'b' the youngest start
-        revision must equal REVISION1.
+        revision must equal SOURCE->rev1.
 
    Note: If the first svn_merge_range_t * element of some subtree child's
    remaining_ranges array is the same as the first range of that child's
@@ -4886,17 +4853,14 @@ remove_children_with_deleted_mergeinfo(m
    DEPTH, NOTIFY_B, and MERGE_B are cascasded from do_directory_merge(), see
    that function for more info.
 
-   If MERGE_B->sources_ancestral is set, then URL1@REVISION1 must be a
-   historical ancestor of URL2@REVISION2, or vice-versa (see
+   If MERGE_B->sources_ancestral is set, then SOURCE->url1@rev1 must be a
+   historical ancestor of SOURCE->url2@rev2, or vice-versa (see
    `MERGEINFO MERGE SOURCE NORMALIZATION' for more requirements around
-   the values of URL1, REVISION1, URL2, and REVISION2 in this case).
+   SOURCE in this case).
 */
 static svn_error_t *
 drive_merge_report_editor(const char *target_abspath,
-                          const char *url1,
-                          svn_revnum_t revision1,
-                          const char *url2,
-                          svn_revnum_t revision2,
+                          const merge_source_t *source,
                           const apr_array_header_t *children_with_mergeinfo,
                           svn_depth_t depth,
                           notification_receiver_baton_t *notify_b,
@@ -4910,18 +4874,18 @@ drive_merge_report_editor(const char *ta
   svn_revnum_t target_start;
   svn_boolean_t honor_mergeinfo;
   const char *old_sess2_url;
-  svn_boolean_t is_rollback = revision1 > revision2;
+  svn_boolean_t is_rollback = source->rev1 > source->rev2;
 
   honor_mergeinfo = HONOR_MERGEINFO(merge_b);
 
   /* Start with a safe default starting revision for the editor and the
      merge target. */
-  target_start = revision1;
+  target_start = source->rev1;
 
   /* If we are honoring mergeinfo the starting revision for the merge target
-     might not be REVISION1, in fact the merge target might not need *any*
-     part of REVISION1:REVISION2 merged -- Instead some subtree of the target
-     needs REVISION1:REVISION2 -- So get the right starting revision for the
+     might not be SOURCE->rev1, in fact the merge target might not need *any*
+     part of SOURCE merged -- Instead some subtree of the target
+     needs SOURCE -- So get the right starting revision for the
      target. */
   if (honor_mergeinfo)
     {
@@ -4942,22 +4906,22 @@ drive_merge_report_editor(const char *ta
       if (child->remaining_ranges->nelts == 0)
         {
           /* The merge target doesn't need anything merged. */

[... 4692 lines stripped ...]