subversion-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From hwri...@apache.org
Subject svn commit: r924028 [1/2] - in /subversion/branches/1.6.x: ./ subversion/include/private/ subversion/libsvn_client/ subversion/libsvn_subr/ subversion/tests/cmdline/
Date Tue, 16 Mar 2010 22:27:37 GMT
Author: hwright
Date: Tue Mar 16 22:27:36 2010
New Revision: 924028

URL: http://svn.apache.org/viewvc?rev=924028&view=rev
Log:
Reintegrate the 1.6.x-issue3432 branch:

 * r878216, r878341, r878343, r876862, r877203, r877595, r877665, r877597, r878269,
   r878321, r878399, r878423, r878426
   Fix issue #3432 'Merge can record mergeinfo from natural history gaps'
   Justification:
     Gaps in merge sources can lead to the creation of invalid mergeinfo
     or the removal of valid mergeinfo.
   Notes:     
     The fix itself in r878399 is relatively simple, but recent refactoring
     of the merge code on trunk makes it look a lot worse than it is.
     Unless otherwise noted, all of the following are nominated simply to
     avoid a conflict nightmare:
     r878216, r878341, r878343 - New test and test tweaks for the issue.
     r876862 - Comment changes only.
     r877203 - Scope reduction of a pool.
     r877595 - Minor bug fix and some refactoring.
     r877665 - Comment changes only.
     r877597 - Merge code refactoring.
     r878269 - Pool use cleanup, no functional change.
     r878321 - More merge code refactoring.
     r878423 - Variable rename to avoid shadowing a previous local.
     r878426 - Remove an unused variable.
   Branch:
     ^/subversion/branches/1.6.x-issue3432
   Votes:
     +1: pburba, stsp, rhuijben

Modified:
    subversion/branches/1.6.x/   (props changed)
    subversion/branches/1.6.x/CHANGES   (props changed)
    subversion/branches/1.6.x/STATUS
    subversion/branches/1.6.x/subversion/include/private/svn_mergeinfo_private.h
    subversion/branches/1.6.x/subversion/libsvn_client/merge.c
    subversion/branches/1.6.x/subversion/libsvn_client/mergeinfo.h
    subversion/branches/1.6.x/subversion/libsvn_subr/mergeinfo.c
    subversion/branches/1.6.x/subversion/tests/cmdline/merge_tests.py

Propchange: subversion/branches/1.6.x/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Tue Mar 16 22:27:36 2010
@@ -4,6 +4,7 @@
 /subversion/branches/1.6.x-dirent-basename:872475-872607
 /subversion/branches/1.6.x-future-proof:880259-884209
 /subversion/branches/1.6.x-issue-3242-partial:916088-923778
+/subversion/branches/1.6.x-issue3432:878598-924025
 /subversion/branches/1.6.x-issue3443:874942-875825
 /subversion/branches/1.6.x-issue3519:880376-889934
 /subversion/branches/1.6.x-issue3573:906488-923765
@@ -63,4 +64,4 @@
 /subversion/branches/tc_url_rev:870696-870828
 /subversion/branches/tree-conflicts:864636-869499
 /subversion/branches/tree-conflicts-notify:870271-870353
-/subversion/trunk:875976,875980-875981,876054-876056,876092,876175,876299,876306,876427,876440,876450,876507,876571,879093,879688,880274-880275,880370,880450,880474,880525-880526,880552,881905,884842,886164,886197,888715,888979,889081,889840,891672,892050,892085,895514,895653,896522,898963,899826,899828,900797,901752,904301,904394,904594,905303,905326,906256,906305,917640,918211,922516
+/subversion/trunk:875976,875980-875981,876054-876056,876092,876175,876299,876306,876427,876440,876450,876507,876571,876862,877203,877595,877597,877665,878216,878269,878321,878341,878343,878399,878423,878426,879093,879688,880274-880275,880370,880450,880474,880525-880526,880552,881905,884842,886164,886197,888715,888979,889081,889840,891672,892050,892085,895514,895653,896522,898963,899826,899828,900797,901752,904301,904394,904594,905303,905326,906256,906305,917640,918211,922516

Propchange: subversion/branches/1.6.x/CHANGES
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Tue Mar 16 22:27:36 2010
@@ -4,6 +4,7 @@
 /subversion/branches/1.6.x-dirent-basename/CHANGES:872475-872607
 /subversion/branches/1.6.x-future-proof/CHANGES:880259-884209
 /subversion/branches/1.6.x-issue-3242-partial/CHANGES:916088-923778
+/subversion/branches/1.6.x-issue3432/CHANGES:878598-924025
 /subversion/branches/1.6.x-issue3443/CHANGES:874942-875825
 /subversion/branches/1.6.x-issue3519/CHANGES:880376-889934
 /subversion/branches/1.6.x-issue3573/CHANGES:906488-923765

Modified: subversion/branches/1.6.x/STATUS
URL: http://svn.apache.org/viewvc/subversion/branches/1.6.x/STATUS?rev=924028&r1=924027&r2=924028&view=diff
==============================================================================
--- subversion/branches/1.6.x/STATUS (original)
+++ subversion/branches/1.6.x/STATUS Tue Mar 16 22:27:36 2010
@@ -179,29 +179,3 @@ Veto-blocked changes:
 
 Approved changes:
 =================
-
- * r878216, r878341, r878343, r876862, r877203, r877595, r877665, r877597, r878269,
-   r878321, r878399, r878423, r878426
-   Fix issue #3432 'Merge can record mergeinfo from natural history gaps'
-   Justification:
-     Gaps in merge sources can lead to the creation of invalid mergeinfo
-     or the removal of valid mergeinfo.
-   Notes:     
-     The fix itself in r878399 is relatively simple, but recent refactoring
-     of the merge code on trunk makes it look a lot worse than it is.
-     Unless otherwise noted, all of the following are nominated simply to
-     avoid a conflict nightmare:
-     r878216, r878341, r878343 - New test and test tweaks for the issue.
-     r876862 - Comment changes only.
-     r877203 - Scope reduction of a pool.
-     r877595 - Minor bug fix and some refactoring.
-     r877665 - Comment changes only.
-     r877597 - Merge code refactoring.
-     r878269 - Pool use cleanup, no functional change.
-     r878321 - More merge code refactoring.
-     r878423 - Variable rename to avoid shadowing a previous local.
-     r878426 - Remove an unused variable.
-   Branch:
-     ^/subversion/branches/1.6.x-issue3432
-   Votes:
-     +1: pburba, stsp, rhuijben

Modified: subversion/branches/1.6.x/subversion/include/private/svn_mergeinfo_private.h
URL: http://svn.apache.org/viewvc/subversion/branches/1.6.x/subversion/include/private/svn_mergeinfo_private.h?rev=924028&r1=924027&r2=924028&view=diff
==============================================================================
--- subversion/branches/1.6.x/subversion/include/private/svn_mergeinfo_private.h (original)
+++ subversion/branches/1.6.x/subversion/include/private/svn_mergeinfo_private.h Tue Mar 16 22:27:36 2010
@@ -33,6 +33,12 @@ extern "C" {
 #endif /* __cplusplus */
 
 
+/* Set inheritability of all ranges in RANGELIST to INHERITABLE.
+   If RANGELIST is NULL do nothing. */
+void
+svn_rangelist__set_inheritance(apr_array_header_t *rangelist,
+                               svn_boolean_t inheritable);
+
 /* Return whether INFO1 and INFO2 are equal in *IS_EQUAL.
 
    CONSIDER_INERITANCE determines how the rangelists in the two

Modified: subversion/branches/1.6.x/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/1.6.x/subversion/libsvn_client/merge.c?rev=924028&r1=924027&r2=924028&view=diff
==============================================================================
--- subversion/branches/1.6.x/subversion/libsvn_client/merge.c (original)
+++ subversion/branches/1.6.x/subversion/libsvn_client/merge.c Tue Mar 16 22:27:36 2010
@@ -131,10 +131,17 @@
  * merge target and any of its subtrees which have explicit mergeinfo
  * or otherwise need special attention during a merge.
  *
- * CHILDREN_WITH_MERGEINFO is initially created by get_mergeinfo_paths()
- * and outside of that function and its helpers should always meet the
- * criteria dictated in get_mergeinfo_paths()'s doc string.  The elements
- * of CHILDREN_WITH_MERGINFO should never be NULL.
+ * During mergeinfo unaware merges, CHILDREN_WITH_MERGEINFO is created by
+ * do_mergeinfo_unaware_dir_merge and contains only one element describing
+ * a contiguous range to be merged to the WC merge target.
+ *
+ * During mergeinfo aware merges CHILDREN_WITH_MERGEINFO is created
+ * by get_mergeinfo_paths() and outside of that function and its helpers
+ * should always meet the criteria dictated in get_mergeinfo_paths()'s doc
+ * string.  The elements of CHILDREN_WITH_MERGINFO should never be NULL.
+ *
+ * For clarification on mergeinfo aware vs. mergeinfo unaware merges, see
+ * the doc string for honor_mergeinfo().
  */
 
 /*-----------------------------------------------------------------------*/
@@ -244,6 +251,13 @@ typedef struct merge_cmd_baton_t {
      reflect the merge_source_t *currently* being merged by do_merge(). */
   merge_source_t merge_source;
 
+  /* Rangelist containing single range which describes the gap, if any,
+     in the natural history of the merge source currently being processed.
+     See http://subversion.tigris.org/issues/show_bug.cgi?id=3432.
+     Updated during each call to do_directory_merge().  May be NULL if there
+     is no gap. */
+  apr_array_header_t *implicit_src_gap;
+
   svn_client_ctx_t *ctx;              /* Client context for callbacks, etc. */
 
   /* Whether invocation of the merge_file_added() callback required
@@ -591,7 +605,7 @@ tree_conflict_on_add(merge_cmd_baton_t *
    set it to false.
 
    If *HONOR_MERGEINFO is set to TRUE and the merge is not a dry run then set
-   *RECORD_MERGEINFO  to true, otherwise set it to false.
+   *RECORD_MERGEINFO to true, otherwise set it to false.
    **/
 static APR_INLINE void
 mergeinfo_behavior(svn_boolean_t *honor_mergeinfo_p,
@@ -3312,8 +3326,12 @@ filter_merged_revisions(svn_client__merg
    been merged to CHILD->PATH and populate CHILD->REMAINING_RANGES with the
    ranges that still need merging.
 
-   URL1, REVISION1, URL2, REVISION2, TARGET_MERGEINFO, ADM_ACCESS, and CTX
-   are all cascaded from the caller's arguments of the same names.
+   URL1, REVISION1, URL2, REVISION2, ADM_ACCESS, 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
+   `MERGEINFO MERGE SOURCE NORMALIZATION'.
+
+   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.
@@ -3333,6 +3351,9 @@ filter_merged_revisions(svn_client__merg
    If IS_SUBTREE is FALSE then PARENT is ignored, otherwise PARENT must
    represent the nearest working copy ancestor of CHILD.
 
+   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.
+
    NOTE: This should only be called when honoring mergeinfo.
 
    NOTE: If PARENT is present then this function must have previously been
@@ -3354,6 +3375,7 @@ calculate_remaining_ranges(svn_client__m
                            const char *url2,
                            svn_revnum_t revision2,
                            svn_mergeinfo_t target_mergeinfo,
+                           apr_array_header_t *implicit_src_gap,
                            svn_boolean_t is_subtree,
                            svn_boolean_t child_inherits_implicit,
                            svn_ra_session_t *ra_session,
@@ -3364,16 +3386,45 @@ calculate_remaining_ranges(svn_client__m
 {
   const char *mergeinfo_path;
   const char *primary_url = (revision1 < revision2) ? url2 : url1;
+  svn_mergeinfo_t adjusted_target_mergeinfo = NULL;
 
   /* Determine which of the requested ranges to consider merging... */
   SVN_ERR(svn_client__path_relative_to_root(&mergeinfo_path, primary_url,
                                             source_root_url, TRUE,
                                             ra_session, NULL, 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
+     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
+     *don't* want to consider those revisions merged already. */
+  if (implicit_src_gap && child->pre_merge_mergeinfo)
+    {
+      apr_array_header_t *explicit_mergeinfo_gap_ranges =
+        apr_hash_get(child->pre_merge_mergeinfo, mergeinfo_path,
+                     APR_HASH_KEY_STRING);
+
+      if (explicit_mergeinfo_gap_ranges)
+        {
+          svn_mergeinfo_t gap_mergeinfo = apr_hash_make(pool);
+          
+          apr_hash_set(gap_mergeinfo, mergeinfo_path, APR_HASH_KEY_STRING,
+                       implicit_src_gap);
+          SVN_ERR(svn_mergeinfo__remove2(&adjusted_target_mergeinfo,
+                                         gap_mergeinfo, target_mergeinfo,
+                                         FALSE, pool, pool));
+        }
+    }
+  else
+    {
+      adjusted_target_mergeinfo = target_mergeinfo;
+    }
+
   /* Initialize CHILD->REMAINING_RANGES and filter out revisions already
      merged (or, in the case of reverse merges, ranges not yet merged). */
   SVN_ERR(filter_merged_revisions(parent, child, entry, mergeinfo_path,
-                                  target_mergeinfo,
+                                  adjusted_target_mergeinfo,
                                   revision1, revision2,
                                   child_inherits_implicit,
                                   ra_session, adm_access, ctx, pool));
@@ -3496,13 +3547,148 @@ calculate_remaining_ranges(svn_client__m
   return SVN_NO_ERROR;
 }
 
+/* Helper for populate_remaining_ranges().
+
+   URL1, REVISION1, URL2, REVISION2, 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.
+
+   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.
+
+   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
+   return GAP_START:GAP_END of 2:6 in this case.  Note that a path 'trunk'
+   might exist at r3-6, but it would not be on the same line of history as
+   trunk@9. */
+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,
+                                  svn_ra_session_t *ra_session,
+                                  merge_cmd_baton_t *merge_b,
+                                  apr_pool_t *scratch_pool,
+                                  apr_pool_t *result_pool)
+{
+  svn_mergeinfo_t implicit_src_mergeinfo;
+  svn_opt_revision_t peg_rev;
+  svn_revnum_t young_rev = MAX(revision1, revision2);
+  svn_revnum_t old_rev = MIN(revision1, revision2);
+  apr_array_header_t *rangelist;
+  const char *url = (revision2 < revision1) ? url1 : url2;
+
+  /* Start by assuming there is no gap. */
+  *gap_start = *gap_end = SVN_INVALID_REVNUM;
+
+  /* Get URL1@REVISION1:URL2@REVISION2 as mergeinfo. */
+  peg_rev.kind = svn_opt_revision_number;
+  peg_rev.value.number = young_rev;
+  SVN_ERR(svn_client__get_history_as_mergeinfo(&implicit_src_mergeinfo, url,
+                                               &peg_rev, young_rev, old_rev,
+                                               ra_session, NULL,
+                                               merge_b->ctx, scratch_pool));
+
+  rangelist = apr_hash_get(implicit_src_mergeinfo,
+                           merge_src_canon_path,
+                           APR_HASH_KEY_STRING);
+
+  if (rangelist) /* ### Can we ever not find a rangelist? */
+    {
+      /* A gap in natural history can result from either a copy or
+         a rename.  If from a copy then history as mergeinfo will look
+         something like this:
+         
+           '/trunk:X,Y-Z'
+         
+         If from a rename it will look like this:
+
+           '/trunk_old_name:X'
+           '/trunk_new_name:Y-Z'
+      
+        In both cases the gap, if it exists, is M-N, where M = X + 1 and
+        N = Y - 1.
+
+        Note that per the rules of 'MERGEINFO MERGE SOURCE NORMALIZATION' we
+        should never have multiple gaps, e.g. if we see anything like the
+        following then something is quite wrong:
+
+            '/trunk_old_name:A,B-C'
+            '/trunk_new_name:D-E'
+      */
+
+      if (rangelist->nelts > 1) /* Copy */
+        {
+          /* 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_end = (APR_ARRAY_IDX(rangelist,
+                                    rangelist->nelts - 1,
+                                    svn_merge_range_t *))->start;
+        }
+      else if (apr_hash_count(implicit_src_mergeinfo) > 1) /* Rename */
+        {
+          apr_array_header_t *requested_rangelist =
+            init_rangelist(MIN(revision1, revision2),
+                           MAX(revision1, revision2),
+                           TRUE, scratch_pool);
+          apr_array_header_t *implicit_rangelist =
+            apr_array_make(scratch_pool, 2, sizeof(svn_merge_range_t *));
+          apr_array_header_t *gap_rangelist;
+          apr_hash_index_t *hi;
+
+          for (hi = apr_hash_first(scratch_pool, implicit_src_mergeinfo);
+               hi;
+               hi = apr_hash_next(hi))
+            {
+              void *value;
+
+              apr_hash_this(hi, NULL, NULL, &value);
+              SVN_ERR(svn_rangelist_merge(&implicit_rangelist, value,
+                                          scratch_pool));
+            }
+          SVN_ERR(svn_rangelist_remove(&gap_rangelist, implicit_rangelist,
+                                       requested_rangelist, FALSE,
+                                       scratch_pool));
+
+          /* If there is anything left it is the gap. */
+          if (gap_rangelist->nelts)
+            {
+              svn_merge_range_t *gap_range =
+                APR_ARRAY_IDX(gap_rangelist, 0, svn_merge_range_t *);
+
+              *gap_start = gap_range->start;
+              *gap_end = gap_range->end;
+            }
+        }
+    }
+
+  return SVN_NO_ERROR;
+}
+
 /* Helper for do_directory_merge().
 
    For each child in CHILDREN_WITH_MERGEINFO, populate that
    child's remaining_ranges list.  CHILDREN_WITH_MERGEINFO is expected
    to be sorted in depth first order and each child must be processed in
-   that order.  All persistent allocations are from
-   CHILDREN_WITH_MERGEINFO->pool.
+   that order.  All persistent allocations are from POOL.  The inheritability
+   of all calculated ranges is TRUE.
 
    If HONOR_MERGEINFO is set, this function will actually try to be
    intelligent about populating remaining_ranges list.  Otherwise, it
@@ -3523,18 +3709,18 @@ populate_remaining_ranges(apr_array_head
                           svn_revnum_t revision1,
                           const char *url2,
                           svn_revnum_t revision2,
-                          svn_boolean_t inheritable,
                           svn_boolean_t honor_mergeinfo,
                           svn_ra_session_t *ra_session,
                           const char *parent_merge_src_canon_path,
                           svn_wc_adm_access_t *adm_access,
-                          merge_cmd_baton_t *merge_b)
+                          merge_cmd_baton_t *merge_b,
+                          apr_pool_t *pool)
 {
-  apr_pool_t *iterpool, *pool;
+  apr_pool_t *iterpool;
   int merge_target_len = strlen(merge_b->target);
   int i;
+  svn_revnum_t gap_start, gap_end;
 
-  pool = children_with_mergeinfo->pool;
   iterpool = svn_pool_create(pool);
 
   /* If we aren't honoring mergeinfo or this is a --record-only merge,
@@ -3551,7 +3737,7 @@ populate_remaining_ranges(apr_array_head
 
           range->start = revision1;
           range->end = revision2;
-          range->inheritable = inheritable;
+          range->inheritable = TRUE;
 
           child->remaining_ranges =
             apr_array_make(pool, 1, sizeof(svn_merge_range_t *));
@@ -3560,6 +3746,31 @@ populate_remaining_ranges(apr_array_head
       return SVN_NO_ERROR;
     }
 
+  /* 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.
+
+     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,
+                                            ra_session, merge_b,
+                                            iterpool, pool));
+
+  /* Stash any gap in the merge command baton, we'll need it later when
+     recording mergeinfo describing this merge. */
+  if (SVN_IS_VALID_REVNUM(gap_start) && SVN_IS_VALID_REVNUM(gap_end))
+    merge_b->implicit_src_gap = init_rangelist(gap_start, gap_end, TRUE,
+                                               pool);
+
   for (i = 0; i < children_with_mergeinfo->nelts; i++)
     {
       const char *child_repos_path;
@@ -3606,52 +3817,6 @@ populate_remaining_ranges(apr_array_head
         MIN(revision1, revision2),
         adm_access, merge_b->ctx, pool));
 
-      /* If, in the merge target's history, there was a copy from a older
-         source which didn't exist at the time of the copy, then URL2 won't
-         exist at some range M:N, where REVISION1 < M < N < REVISION2.  If
-         there were multiple instances of such copies then there will be
-         multiple 'gaps' like this.  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.  In other words, we can't try to
-         merge any part of URL2@M:URL2@N since no part of that actually
-         exists and would break the editor.  We can "ignore" these gaps by
-         adjusting the target's implicit mergeinfo to make it look like they
-         are already part of the target's natural history. */
-      if (i == 0) /* CHILDREN_WITH_MERGEINFO[0] is always the target. */
-        {
-          apr_hash_index_t *hi;
-          svn_revnum_t youngest_rev, oldest_rev;
-
-          SVN_ERR(svn_mergeinfo__get_range_endpoints(
-            &youngest_rev, &oldest_rev, child->implicit_mergeinfo,
-            iterpool));
-
-          /* Adjust the target's implicit mergeinfo so there are no gaps */
-          for (hi = apr_hash_first(iterpool, child->implicit_mergeinfo);
-               hi;
-               hi = apr_hash_next(hi))
-            {
-              const void *key;
-              void *value;
-              const char *source_path;
-              apr_array_header_t *source_rangelist, *rev1_rev2_rangelist;
-
-              apr_hash_this(hi, &key, NULL, &value);
-              source_path = key;
-              source_rangelist = value;
-              youngest_rev = (APR_ARRAY_IDX(source_rangelist,
-                                            source_rangelist->nelts - 1,
-                                            svn_merge_range_t *))->end;
-              rev1_rev2_rangelist = init_rangelist(oldest_rev, youngest_rev,
-                                                   TRUE, iterpool);
-              svn_rangelist_merge(&source_rangelist, rev1_rev2_rangelist,
-                                  pool);
-              apr_hash_set(child->implicit_mergeinfo, source_path,
-                           APR_HASH_KEY_STRING, source_rangelist);
-             }
-        }
-
       /* If CHILD isn't the merge target find its parent. */
       if (i > 0)
         {
@@ -3677,11 +3842,82 @@ populate_remaining_ranges(apr_array_head
                                          child_url1, revision1,
                                          child_url2, revision2,
                                          child->pre_merge_mergeinfo,
+                                         merge_b->implicit_src_gap,
                                          i > 0, /* is subtree */
                                          child_inherits_implicit,
                                          ra_session, child_entry,
                                          adm_access, merge_b->ctx,
                                          pool));
+
+      /* Deal with any gap in URL1@REVISION1:URL2@REVISION2'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.
+         
+         If the gap exactly matches or is a superset of a range in
+         CHILD->REMAINING_RANGES then we must remove that range so we don't
+         attempt to describe non-existent paths via the reporter, this will
+         break the editor and our merge.
+
+         If the gap adjoins or overlaps a range in CHILD->REMAINING_RANGES
+         then we must *add* the gap so we span the missing revisions. */
+      if (child->remaining_ranges->nelts
+          && merge_b->implicit_src_gap)
+        {
+          int j;
+          svn_revnum_t start, end;
+          svn_boolean_t proper_subset = FALSE;
+          svn_boolean_t equals = 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)
+            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))
+                {
+                  proper_subset = TRUE;
+                  break;
+                }
+              else if ((gap_start == start) && (end == gap_end))
+                {
+                  equals = TRUE;
+                  break;
+                }
+              else if (gap_start <= end && start <= gap_end)  /* intersect */
+                {
+                  overlaps_or_adjoins = TRUE;
+                  break;
+                }
+            }
+
+          if (!proper_subset)
+            {
+              /* We need to make adjustements.  Remove from, or add the gap
+                 to, CHILD->REMAINING_RANGES as appropriate. */
+
+              if (overlaps_or_adjoins)
+                SVN_ERR(svn_rangelist_merge(&(child->remaining_ranges),
+                                            merge_b->implicit_src_gap,
+                                            pool));
+              else /* equals == TRUE */
+                SVN_ERR(svn_rangelist_remove(&(child->remaining_ranges),
+                                             merge_b->implicit_src_gap,
+                                             child->remaining_ranges, FALSE,
+                                             pool));
+            }
+
+          if (revision1 > revision2) /* Reverse merge */
+            svn_rangelist_reverse(child->remaining_ranges, iterpool);
+        }
     }
 
   svn_pool_destroy(iterpool);
@@ -3693,109 +3929,51 @@ populate_remaining_ranges(apr_array_head
 
 /*** Other Helper Functions ***/
 
+/* Helper for record_mergeinfo_for_dir_merge().
 
-/* Create mergeinfo describing the merge of RANGELIST into TARGET_WCPATH,
-   accounting for paths unaffected by the merge due to skips or conflicts from
-   NOTIFY_B. For 'immediates' merge it sets an inheritable mergeinfo
-   corresponding to current merge on merge target. For 'files' merge it sets
-   an inheritable mergeinfo corrsponding to current merge on merged files.
-   If TARGET_WCPATH is a directory and it is missing an immediate child then
-   TARGET_MISSING_CHILD should be true, otherwise it is false.*/
-static svn_error_t *
-determine_merges_performed(apr_hash_t **merges, const char *target_wcpath,
-                           apr_array_header_t *rangelist,
-                           svn_depth_t depth,
-                           svn_wc_adm_access_t *adm_access,
-                           notification_receiver_baton_t *notify_b,
-                           merge_cmd_baton_t *merge_b,
-                           apr_pool_t *pool)
-{
-  apr_size_t nbr_skips = (notify_b->skipped_paths != NULL ?
-                          apr_hash_count(notify_b->skipped_paths) : 0);
-
-  *merges = apr_hash_make(pool);
-  apr_hash_set(*merges, target_wcpath, APR_HASH_KEY_STRING, rangelist);
-  if (nbr_skips > 0)
-    {
-      apr_hash_index_t *hi;
-
-      /* Override the mergeinfo for child paths which weren't
-         actually merged. */
-      for (hi = apr_hash_first(NULL, notify_b->skipped_paths); hi;
-           hi = apr_hash_next(hi))
-        {
-          const void *skipped_path;
-          svn_wc_status2_t *status;
-
-          apr_hash_this(hi, &skipped_path, NULL, NULL);
-
-          /* Before we override, make sure this is a versioned path, it
-             might be an unversioned obstruction. */
-          SVN_ERR(svn_wc_status2(&status, (const char *) skipped_path,
-                                 adm_access, pool));
-          if (status->text_status == svn_wc_status_none
-              || status->text_status == svn_wc_status_unversioned)
-            continue;
-
-          /* Add an empty range list for this path.
-
-             ### TODO: This works fine for a file path skipped because it is
-             ### missing as long as the file's parent directory is present.
-             ### But missing directory paths skipped are not handled yet,
-             ### see issue #2915. */
-          apr_hash_set(*merges, (const char *) skipped_path,
-                       APR_HASH_KEY_STRING,
-                       apr_array_make(pool, 0, sizeof(svn_merge_range_t)));
-
-          if (nbr_skips < notify_b->nbr_notifications)
-            /* ### Use RANGELIST as the mergeinfo for all children of
-               ### this path which were not also explicitly
-               ### skipped? */
-            ;
+   Adjust, in place, the inheritability of the ranges in RANGELIST to
+   describe a merge of RANGELIST into WC_WCPATH at depth DEPTH.  ENTRY
+   is the entry for WC_PATH.
+   
+   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(). */
+static svn_error_t *
+calculate_merge_inheritance(apr_array_header_t *rangelist,
+                            const char *wc_path,
+                            const svn_wc_entry_t *entry,
+                            svn_boolean_t wc_path_is_merge_target,
+                            svn_boolean_t wc_path_has_missing_child,
+                            svn_depth_t depth)
+{
+  if (entry->kind == svn_node_file)
+    {
+      /* Files *never* have non-inheritable mergeinfo. */
+      svn_rangelist__set_inheritance(rangelist, TRUE);
+    }
+  else if (entry->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);
+          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);
+          else /* depth == infinity */
+            svn_rangelist__set_inheritance(rangelist, TRUE);  
         }
     }
-  if ((depth != svn_depth_infinity) && notify_b->merged_paths)
-    {
-      apr_hash_index_t *hi;
-      const void *merged_path;
-
-      for (hi = apr_hash_first(NULL, notify_b->merged_paths); hi;
-           hi = apr_hash_next(hi))
-        {
-          const svn_wc_entry_t *child_entry;
-          apr_array_header_t *rangelist_of_child = NULL;
-          apr_hash_this(hi, &merged_path, NULL, NULL);
-          SVN_ERR(svn_wc__entry_versioned(&child_entry,
-                                          merged_path,
-                                          adm_access, FALSE,
-                                          pool));
-          if (((child_entry->kind == svn_node_dir)
-               && (strcmp(merge_b->target, merged_path) == 0)
-               && (depth == svn_depth_immediates))
-              || ((child_entry->kind == svn_node_file)
-                  && (depth == svn_depth_files)))
-            {
-              /* Set the explicit inheritable mergeinfo for,
-                 1. Merge target directory if depth is immediates.
-                 2. If merge is on a file and requested depth is 'files'.
-               */
-              int i;
-              rangelist_of_child = svn_rangelist_dup(rangelist, pool);
-              for (i = 0; i < rangelist_of_child->nelts; i++)
-                {
-                  svn_merge_range_t *rng =
-                    APR_ARRAY_IDX(rangelist_of_child, i, svn_merge_range_t *);
-                  rng->inheritable = TRUE;
-                }
-            }
-          if (rangelist_of_child)
-            {
-              apr_hash_set(*merges, (const char *)merged_path,
-                           APR_HASH_KEY_STRING, rangelist_of_child);
-            }
-        }
-    }
-
   return SVN_NO_ERROR;
 }
 
@@ -3928,6 +4106,84 @@ update_wc_mergeinfo(const char *target_w
   return SVN_NO_ERROR;
 }
 
+/* Helper for record_mergeinfo_for_dir_merge().
+
+   Record override mergeinfo on any paths skipped during a merge.
+
+   Set empty mergeinfo on each path in NOTIFY_B->SKIPPED_PATHS so the path
+   does not incorrectly inherit mergeinfo that will later be describing
+   the merge.
+
+   MERGEINFO_PATH, ADM_ACCESS, NOTIFY_B, and MERGE_B are all cascased from
+   arguments of the same name in the caller.
+
+   TARGET_ENTRY is the entry from MERGE_B->TARGET.  IS_ROLLBACK is true if
+   the caller is recording a reverse merge and false otherwise.  RANGELIST
+   is the set of revisions being merged from MERGEINFO_PATH to
+   MERGE_B->TARGET. */
+static svn_error_t *
+record_skips(const char *mergeinfo_path,
+             const svn_wc_entry_t *target_entry,
+             apr_array_header_t *rangelist,
+             svn_boolean_t is_rollback,
+             svn_wc_adm_access_t *adm_access,
+             notification_receiver_baton_t *notify_b,
+             merge_cmd_baton_t *merge_b,
+             apr_pool_t *pool)
+{
+  apr_size_t nbr_skips = (notify_b->skipped_paths != NULL ?
+                          apr_hash_count(notify_b->skipped_paths) : 0);
+
+  if (nbr_skips > 0)
+    {
+      apr_hash_index_t *hi;
+      apr_hash_t *merges = apr_hash_make(pool);
+
+      /* Override the mergeinfo for child paths which weren't
+         actually merged. */
+      for (hi = apr_hash_first(NULL, notify_b->skipped_paths); hi;
+           hi = apr_hash_next(hi))
+        {
+          const void *skipped_path;
+          svn_wc_status2_t *status;
+
+          apr_hash_this(hi, &skipped_path, NULL, NULL);
+
+          /* Before we override, make sure this is a versioned path, it
+             might be an unversioned obstruction. */
+          SVN_ERR(svn_wc_status2(&status, skipped_path, adm_access, pool));
+          if (status->text_status == svn_wc_status_none
+              || status->text_status == svn_wc_status_unversioned)
+            continue;
+
+          /* Add an empty range list for this path.
+
+             ### TODO: This works fine for a file path skipped because it is
+             ### missing as long as the file's parent directory is present.
+             ### But missing directory paths skipped are not handled yet,
+             ### see issue #2915.
+             
+             ### TODO: An empty range is fine if the skipped path doesn't
+             ### inherit any mergeinfo from a parent, but if it does
+             ### we need to account for that.  See issue #3440
+             ### http://subversion.tigris.org/issues/show_bug.cgi?id=3440. */
+          apr_hash_set(merges, (const char *) skipped_path,
+                       APR_HASH_KEY_STRING,
+                       apr_array_make(pool, 0, sizeof(svn_merge_range_t)));
+
+          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(merge_b->target, target_entry,
+                                  mergeinfo_path, merges,
+                                  is_rollback, adm_access,
+                                  merge_b->ctx, pool));
+    }
+  return SVN_NO_ERROR;
+}
 
 /* Create and return an error structure appropriate for the unmerged
    revisions range(s). */
@@ -4590,6 +4846,9 @@ struct get_mergeinfo_walk_baton
   /* RA session and client context cascaded from do_directory_merge() */
   svn_ra_session_t *ra_session;
   svn_client_ctx_t *ctx;
+
+  /* Pool from which to allocate new elements of CHILDREN_WITH_MERGEINFO. */
+  apr_pool_t *pool;
 };
 
 
@@ -4597,12 +4856,13 @@ struct get_mergeinfo_walk_baton
 
    Given PATH, its corresponding ENTRY, and WB, where WB is the WALK_BATON
    of type "struct get_mergeinfo_walk_baton *":  If PATH is switched,
-   has explicit working svn:mergeinfo from a corresponding merge source, is
-   missing a child due to a sparse checkout, is absent from disk, is
-   scheduled for deletion, or if the walk is being done as part of a reverse
-   merge, then create a svn_client__merge_path_t *representing *PATH,
-   allocated in WB->CHILDREN_WITH_MERGEINFO->POOL, and push it onto the
-   WB->CHILDREN_WITH_MERGEINFO array. */
+   has explicit working svn:mergeinfo from the corresponding merge source and
+   this walk is being done as part of a forward merge or has *any* explicit
+   working svn:mergeinfo and this walk is being done as part of a reverse
+   merge, is missing a child due to a sparse checkout, is absent from disk,
+   is scheduled for deletion, then create a svn_client__merge_path_t *
+   representing *PATH, allocated in BATON->POOL, and
+   push it onto the WB->CHILDREN_WITH_MERGEINFO array. */
 static svn_error_t *
 get_mergeinfo_walk_cb(const char *path,
                       const svn_wc_entry_t *entry,
@@ -4776,8 +5036,8 @@ get_mergeinfo_walk_cb(const char *path,
           )
     {
       svn_client__merge_path_t *child =
-        apr_pcalloc(wb->children_with_mergeinfo->pool, sizeof(*child));
-      child->path = apr_pstrdup(wb->children_with_mergeinfo->pool, path);
+        apr_pcalloc(wb->pool, sizeof(*child));
+      child->path = apr_pstrdup(wb->pool, path);
       child->missing_child = (entry->depth == svn_depth_empty
                               || entry->depth == svn_depth_files
                               || ((wb->depth == svn_depth_immediates) &&
@@ -4875,12 +5135,13 @@ get_child_with_mergeinfo(const apr_array
 }
 
 /* Insert a deep copy of INSERT_ELEMENT into the CHILDREN_WITH_MERGEINFO
-   array at its correct position.  Allocate the new storage from the array's
-   pool.  CHILDREN_WITH_MERGEINFO is a depth first sorted array of
+   array at its correct position.  Allocate the new storage in POOL.
+   CHILDREN_WITH_MERGEINFO is a depth first sorted array of
    (svn_client__merge_path_t *). */
 static void
 insert_child_to_merge(apr_array_header_t *children_with_mergeinfo,
-                      const svn_client__merge_path_t *insert_element)
+                      const svn_client__merge_path_t *insert_element,
+                      apr_pool_t *pool)
 {
   int insert_index;
   const svn_client__merge_path_t *new_element;
@@ -4890,8 +5151,7 @@ insert_child_to_merge(apr_array_header_t
     svn_sort__bsearch_lower_bound(&insert_element, children_with_mergeinfo,
                                   compare_merge_path_t_as_paths);
 
-  new_element = svn_client__merge_path_dup(insert_element,
-                                           children_with_mergeinfo->pool);
+  new_element = svn_client__merge_path_dup(insert_element, pool);
   svn_sort__array_insert(&new_element, children_with_mergeinfo, insert_index);
 }
 
@@ -4913,7 +5173,7 @@ insert_child_to_merge(apr_array_header_t
    already present in CHILDREN_WITH_MERGEINFO are also added to the array,
    limited by DEPTH (e.g. don't add directory siblings of a switched file).
    Use POOL for temporary allocations only, any new CHILDREN_WITH_MERGEINFO
-   elements are allocated in CHILDREN_WITH_MERGEINFO->POOL. */
+   elements are allocated in POOL. */
 static svn_error_t *
 insert_parent_and_sibs_of_sw_absent_del_entry(
                                    apr_array_header_t *children_with_mergeinfo,
@@ -4943,11 +5203,11 @@ insert_parent_and_sibs_of_sw_absent_del_
   else
     {
       /* Create a new element to insert into CHILDREN_WITH_MERGEINFO. */
-      parent = apr_pcalloc(children_with_mergeinfo->pool, sizeof(*parent));
-      parent->path = apr_pstrdup(children_with_mergeinfo->pool, parent_path);
+      parent = apr_pcalloc(pool, sizeof(*parent));
+      parent->path = apr_pstrdup(pool, parent_path);
       parent->missing_child = TRUE;
       /* Insert PARENT into CHILDREN_WITH_MERGEINFO. */
-      insert_child_to_merge(children_with_mergeinfo, parent);
+      insert_child_to_merge(children_with_mergeinfo, parent, pool);
       /* Increment for loop index so we don't process the inserted element. */
       (*curr_index)++;
     } /*(parent == NULL) */
@@ -4985,11 +5245,12 @@ insert_parent_and_sibs_of_sw_absent_del_
                 continue;
             }
 
-          sibling_of_missing = apr_pcalloc(children_with_mergeinfo->pool,
+          sibling_of_missing = apr_pcalloc(pool,
                                            sizeof(*sibling_of_missing));
-          sibling_of_missing->path = apr_pstrdup(children_with_mergeinfo->pool,
+          sibling_of_missing->path = apr_pstrdup(pool,
                                                  child_path);
-          insert_child_to_merge(children_with_mergeinfo, sibling_of_missing);
+          insert_child_to_merge(children_with_mergeinfo, sibling_of_missing,
+                                pool);
         }
     }
   return SVN_NO_ERROR;
@@ -5002,8 +5263,9 @@ insert_parent_and_sibs_of_sw_absent_del_
    Create an svn_client__merge_path_t * for any path which meets one or more
    of the following criteria:
 
-     1) Path has working svn:mergeinfo from corresponding merge source or
-        has empty mergeinfo.
+     1) do_directory_merge() is processing a forward merge and path has
+        working svn:mergeinfo from corresponding merge source or has empty
+        mergeinfo.
      2) Path is switched.
      3) Path is a subtree of the merge target (i.e. is not equal to
         MERGE_CMD_BATON->TARGET) and has no mergeinfo of its own but its
@@ -5023,7 +5285,8 @@ insert_parent_and_sibs_of_sw_absent_del_
         DEPTH is svn_depth_immediates.
      9) Path is an immediate *file* child of MERGE_CMD_BATON->TARGET and
         DEPTH is svn_depth_files.
-    10) do_directory_merge() is processing a reverse merge.
+    10) do_directory_merge() is processing a reverse merge and path has any
+        working svn:mergeinfo.
 
    If HONOR_MERGEINFO is FALSE, then create an svn_client__merge_path_t * only
    for MERGE_CMD_BATON->TARGET (i.e. only criteria 7 is applied).
@@ -5082,6 +5345,7 @@ get_mergeinfo_paths(apr_array_header_t *
   wb.depth = depth;
   wb.ra_session = ra_session;
   wb.ctx = merge_cmd_baton->ctx;
+  wb.pool = pool;
 
   /* Cover cases 1), 2), and 6), 7), 8), 9), and 10) by walking the WC to get
      all paths which have mergeinfo and/or are switched or are absent from
@@ -5201,13 +5465,14 @@ get_mergeinfo_paths(apr_array_header_t *
                          directory and file children. */
 
                       child_of_noninheritable =
-                        apr_pcalloc(children_with_mergeinfo->pool,
+                        apr_pcalloc(pool,
                                     sizeof(*child_of_noninheritable));
                       child_of_noninheritable->path =
-                        apr_pstrdup(children_with_mergeinfo->pool,
+                        apr_pstrdup(pool,
                                     child_path);
                       insert_child_to_merge(children_with_mergeinfo,
-                                            child_of_noninheritable);
+                                            child_of_noninheritable,
+                                            pool);
                       if (!merge_cmd_baton->dry_run
                           && merge_cmd_baton->same_repos)
                         {
@@ -5236,7 +5501,7 @@ get_mergeinfo_paths(apr_array_header_t *
           /* Case 4 and 5 are handled by the following function. */
           SVN_ERR(insert_parent_and_sibs_of_sw_absent_del_entry(
             children_with_mergeinfo, merge_cmd_baton, &i, child,
-            depth, adm_access, iterpool));
+            depth, adm_access, pool));
         } /* i < children_with_mergeinfo->nelts */
       svn_pool_destroy(iterpool);
     } /* honor_mergeinfo */
@@ -5909,7 +6174,7 @@ do_file_merge(const char *url1,
                                              source_root_url,
                                              url1, revision1, url2, revision2,
                                              target_mergeinfo,
-                                             FALSE,
+                                             merge_b->implicit_src_gap, FALSE,
                                              FALSE, merge_b->ra_session1,
                                              entry, adm_access, ctx, pool));
           remaining_ranges = merge_target->remaining_ranges;
@@ -6105,7 +6370,6 @@ do_file_merge(const char *url1,
      merge ranges, include the noop ones.  */
   if (record_mergeinfo && remaining_ranges->nelts)
     {
-      apr_hash_t *merges;
       apr_array_header_t *filtered_rangelist;
 
       /* Filter any ranges from TARGET_WCPATH's own history, there is no
@@ -6117,13 +6381,15 @@ do_file_merge(const char *url1,
         merge_target->implicit_mergeinfo,
         &range, subpool));
 
-      if (filtered_rangelist->nelts)
+      /* Only record mergeinfo if there is something other than
+         self-referential mergeinfo, but don't record mergeinfo if
+         TARGET_WCPATH was skipped. */
+      if (filtered_rangelist->nelts
+          && (!notify_b->skipped_paths
+              || (apr_hash_count(notify_b->skipped_paths) == 0)))
         {
-          SVN_ERR(determine_merges_performed(&merges, target_wcpath,
-                                             filtered_rangelist,
-                                             svn_depth_infinity,
-                                             adm_access, notify_b,
-                                             merge_b, subpool));
+          apr_hash_t *merges = apr_hash_make(subpool);
+
           /* If merge target has indirect mergeinfo set it before
              recording the first merge range. */
           if (indirect)
@@ -6131,6 +6397,8 @@ do_file_merge(const char *url1,
                                                     target_mergeinfo,
                                                     adm_access, subpool));
 
+          apr_hash_set(merges, target_wcpath, APR_HASH_KEY_STRING,
+                       filtered_rangelist);
           SVN_ERR(update_wc_mergeinfo(target_wcpath, entry, mergeinfo_path,
                                       merges, is_rollback, adm_access,
                                       ctx, subpool));
@@ -6172,10 +6440,8 @@ do_file_merge(const char *url1,
    latter is set to a rangelist equal to the remaining_ranges of the path's
    nearest path-wise ancestor in NOTIFY_B->CHILDREN_WITH_MERGEINFO.
 
-   POOL is used only for temporary allocations, any elements added to
-   NOTIFY_B->CHILDREN_WITH_MERGEINFO are allocated in
-   NOTIFY_B->CHILDREN_WITH_MERGEINFO->POOL.
-   */
+   Any elements added to NOTIFY_B->CHILDREN_WITH_MERGEINFO are allocated
+   in POOL. */
 static svn_error_t *
 process_children_with_new_mergeinfo(merge_cmd_baton_t *merge_b,
                                     notification_receiver_baton_t *notify_b,
@@ -6268,12 +6534,9 @@ process_children_with_new_mergeinfo(merg
                     APR_ARRAY_IDX(notify_b->children_with_mergeinfo,
                                   parent_index,
                                   svn_client__merge_path_t *);
-                  new_child =
-                    apr_pcalloc(notify_b->children_with_mergeinfo->pool,
-                                sizeof(*new_child));
-                  new_child->path =
-                    apr_pstrdup(notify_b->children_with_mergeinfo->pool,
-                                path_with_new_mergeinfo);
+                  new_child = apr_pcalloc(pool, sizeof(*new_child));
+                  new_child->path = apr_pstrdup(pool,
+                                                path_with_new_mergeinfo);
 
                   /* If path_with_new_mergeinfo is the merge target itself
                      then it should already be in
@@ -6288,10 +6551,9 @@ process_children_with_new_mergeinfo(merg
 
                   /* Set the path's remaining_ranges equal to its parent's. */
                   new_child->remaining_ranges = svn_rangelist_dup(
-                     parent->remaining_ranges,
-                     notify_b->children_with_mergeinfo->pool);
+                     parent->remaining_ranges, pool);
                   insert_child_to_merge(notify_b->children_with_mergeinfo,
-                                        new_child);
+                                        new_child, pool);
                 }
             }
           /* Return MERGE_B->RA_SESSION2 to its initial state if we
@@ -6305,6 +6567,345 @@ process_children_with_new_mergeinfo(merg
   return SVN_NO_ERROR;
 }
 
+/* Helper for do_directory_merge() when performing mergeinfo unaware merges.
+
+   Merge the diff of URL1@REVISION1:URL2@REVISION2 to TARGET_DIR_WCPATH.
+
+   URL1, REVISION1, URL2, REVISION2, ADM_ACCESS, DEPTH, NOTIFY_B, and MERGE_B
+   are all cascaded from do_directory_merge's arguments of the same names.
+
+   NOTE: This is a very thin wrapper around drive_merge_report_editor() and
+   exists only to populate NOTIFY_B->CHILREN_WITH_MERGEINFO with the single
+   element expected during mergeinfo unaware merges..
+*/
+static svn_error_t *
+do_mergeinfo_unaware_dir_merge(const char *url1,
+                               svn_revnum_t revision1,
+                               const char *url2,
+                               svn_revnum_t revision2,
+                               const char *target_dir_wcpath,
+                               svn_wc_adm_access_t *adm_access,
+                               svn_depth_t depth,
+                               notification_receiver_baton_t *notify_b,
+                               merge_cmd_baton_t *merge_b,
+                               apr_pool_t *pool)
+{
+  /* Initialize NOTIFY_B->CHILDREN_WITH_MERGEINFO and populate it with
+     one element describing the merge of REVISION1:REVISION2 to
+     TARGET_DIR_WCPATH. */
+  svn_client__merge_path_t *item = apr_pcalloc(pool, sizeof(*item));
+  svn_merge_range_t *itemrange = apr_pcalloc(pool, sizeof(*itemrange));
+  apr_array_header_t *remaining_ranges = apr_array_make(
+    pool, 1, sizeof(svn_merge_range_t *));
+
+  itemrange->start = revision1;
+  itemrange->end = revision2;
+  itemrange->inheritable = TRUE;
+
+  APR_ARRAY_PUSH(remaining_ranges, svn_merge_range_t *) = itemrange;
+
+  item->path = apr_pstrdup(pool, target_dir_wcpath);
+  item->remaining_ranges = remaining_ranges;
+  APR_ARRAY_PUSH(notify_b->children_with_mergeinfo,
+                 svn_client__merge_path_t *) = item;
+  return drive_merge_report_editor(target_dir_wcpath,
+                                   url1, revision1, url2, revision2,
+                                   NULL, depth, notify_b,
+                                   adm_access, &merge_callbacks,
+                                   merge_b, pool);
+}
+
+/* Helper for do_directory_merge().
+
+   Record mergeinfo describing a merge of MERGED_RANGE->START:
+   MERGED_RANGE->END from the repository relative path MERGEINFO_PATH to
+   the directory represented by TARGET_ENTRY.  Obviously this should only
+   be called if recording mergeinfo -- see doc string for
+   mergeinfo_behavior().
+
+   TARGET_ENTRY, DEPTH, NOTIFY_B, MERGE_B, and ADM_ACCESS are all cascaded
+   from do_directory_merge's arguments of the same names.
+*/
+static svn_error_t *
+record_mergeinfo_for_dir_merge(const svn_wc_entry_t *target_entry,
+                               svn_merge_range_t *merged_range,
+                               const char *mergeinfo_path,
+                               svn_depth_t depth,
+                               notification_receiver_baton_t *notify_b,
+                               merge_cmd_baton_t *merge_b,
+                               svn_wc_adm_access_t *adm_access,
+                               apr_pool_t *pool)
+{
+  int i;
+  int merge_target_len = strlen(merge_b->target);
+  svn_boolean_t is_rollback = (merged_range->start > merged_range->end);
+
+  /* Update the WC mergeinfo here to account for our new
+     merges, minus any unresolved conflicts and skips. */
+
+  /* We need a scratch pool for iterations below. */
+  apr_pool_t *iterpool = svn_pool_create(pool);
+
+  /* Remove absent children at or under TARGET_WCPATH from
+     NOTIFY_B->SKIPPED_PATHS and NOTIFY_B->CHILDREN_WITH_MERGEINFO
+     before we calculate the merges performed. */
+  remove_absent_children(merge_b->target,
+                         notify_b->children_with_mergeinfo, notify_b);
+
+  for (i = 0; i < notify_b->children_with_mergeinfo->nelts; i++)
+    {
+      const char *child_repos_path;
+      const char *child_merge_src_canon_path;
+      const svn_wc_entry_t *child_entry;
+      apr_array_header_t *child_merge_rangelist;
+      apr_hash_t *child_merges;
+      svn_client__merge_path_t *child =
+                     APR_ARRAY_IDX(notify_b->children_with_mergeinfo, i,
+                                   svn_client__merge_path_t *);
+      SVN_ERR_ASSERT(child);
+      if (child->absent)
+        continue;
+
+      svn_pool_clear(iterpool);
+
+      if (strlen(child->path) == merge_target_len)
+        child_repos_path = "";
+      else
+        child_repos_path = child->path +
+          (merge_target_len ? merge_target_len + 1 : 0);
+      child_merge_src_canon_path = svn_path_join(mergeinfo_path,
+                                                 child_repos_path,
+                                                 iterpool);
+      SVN_ERR(svn_wc__entry_versioned(&child_entry, child->path,
+                                      adm_access, FALSE, iterpool));
+
+      /* Filter any ranges from each child's natural history before
+         setting mergeinfo describing the merge. */
+      SVN_ERR(filter_natural_history_from_mergeinfo(
+        &child_merge_rangelist, child_merge_src_canon_path,
+        child->implicit_mergeinfo, merged_range, iterpool));
+
+      if (child_merge_rangelist->nelts == 0)
+        continue;
+
+      /* If we are here we know we will be recording some mergeinfo, but
+         before we do set override mergeinfo on skipped paths so they
+         don't incorrectly inherit the mergeinfo we are about to set.
+         We only need to do this once. */
+      if (i == 0)
+        SVN_ERR(record_skips(mergeinfo_path,
+                             target_entry, child_merge_rangelist,
+                             is_rollback, adm_access,
+                             notify_b, merge_b, iterpool));
+
+      SVN_ERR(calculate_merge_inheritance(child_merge_rangelist, child->path,
+                                          child_entry, i == 0,
+                                          child->missing_child,
+                                          depth));
+
+      /* If CHILD has indirect mergeinfo set it before
+         recording the first merge range. */
+      if (child->indirect_mergeinfo)
+        SVN_ERR(svn_client__record_wc_mergeinfo(child->path,
+                                                child->pre_merge_mergeinfo,
+                                                adm_access,
+                                                iterpool));
+      if (merge_b->implicit_src_gap)
+        {
+          /* If this is a reverse merge reorder CHILD->REMAINING_RANGES
+             so it will work with the svn_rangelist_remove API. */
+          if (is_rollback)
+            SVN_ERR(svn_rangelist_reverse(child_merge_rangelist, iterpool));
+
+            SVN_ERR(svn_rangelist_remove(&child_merge_rangelist,
+                                         merge_b->implicit_src_gap,
+                                         child_merge_rangelist, FALSE,
+                                         iterpool));
+          if (is_rollback)
+            SVN_ERR(svn_rangelist_reverse(child_merge_rangelist, iterpool));
+        }
+
+      child_merges = apr_hash_make(iterpool);
+      apr_hash_set(child_merges, child->path, APR_HASH_KEY_STRING,
+                   child_merge_rangelist);
+      SVN_ERR(update_wc_mergeinfo(child->path, child_entry,
+                                  child_merge_src_canon_path,
+                                  child_merges, is_rollback,
+                                  adm_access, merge_b->ctx, iterpool));
+
+      /* Elide explicit subtree mergeinfo. */
+      if (i > 0)
+        {
+          svn_boolean_t in_switched_subtree = FALSE;
+
+          if (child->switched)
+            in_switched_subtree = TRUE;
+          else if (i > 1)
+            {
+              /* Check if CHILD is part of a switched subtree */
+              svn_client__merge_path_t *parent;
+              int j = i - 1;
+              for (; j > 0; j--)
+                {
+                  parent = APR_ARRAY_IDX(notify_b->children_with_mergeinfo,
+                                         j, svn_client__merge_path_t *);
+                  if (parent
+                      && parent->switched
+                      && svn_path_is_ancestor(parent->path, child->path))
+                    {
+                      in_switched_subtree = TRUE;
+                      break;
+                    }
+                }
+            }
+
+          /* Allow mergeinfo on switched subtrees to elide to the
+             repository. Otherwise limit elision to the merge target
+             for now.  do_directory_merge() will eventually try to
+             elide that when the merge is complete. */
+          SVN_ERR(svn_client__elide_mergeinfo(
+            child->path,
+            in_switched_subtree ? NULL : merge_b->target,
+            child_entry, adm_access, merge_b->ctx, iterpool));
+        }
+    } /* (i = 0; i < notify_b->children_with_mergeinfo->nelts; i++) */
+
+  svn_pool_destroy(iterpool);
+  return SVN_NO_ERROR;
+}
+
+/* Helper for do_directory_merge().
+
+   Similar to record_mergeinfo_for_dir_merge in that it records mergeinfo
+   describing a merge of MERGED_RANGE->START:MERGED_RANGE->END from the
+   repository relative path MERGEINFO_PATH to the directory represented by
+   TARGET_ENTRY.  Unlike record_mergeinfo_for_dir_merge() though, this
+   funtion only records mergeinfo on *new* subtrees added by the merge which
+   are children of paths with non-inheritable ranges or which have missing
+   siblings - see criteria 3 and 5 in the doc string for get_mergeinfo_paths.
+   See also issue #2829
+   http://subversion.tigris.org/issues/show_bug.cgi?id=2829#desc14.
+
+   DEPTH, NOTIFY_B, MERGE_B, and ADM_ACCESS are all cascaded from
+   do_directory_merge's arguments of the same names.
+*/
+static svn_error_t *
+record_mergeinfo_for_added_subtrees(svn_merge_range_t *merged_range,
+                                    const char *mergeinfo_path,
+                                    svn_depth_t depth,
+                                    notification_receiver_baton_t *notify_b,
+                                    merge_cmd_baton_t *merge_b,
+                                    svn_wc_adm_access_t *adm_access,
+                                    apr_pool_t *pool)
+{
+  /* If no paths were added by the merge then we have nothing to do. */
+  if (notify_b->added_paths)
+    {
+      apr_pool_t *iterpool = svn_pool_create(pool);
+      apr_hash_index_t *hi;
+
+      for (hi = apr_hash_first(NULL, notify_b->added_paths); hi;
+           hi = apr_hash_next(hi))
+        {
+          const void *key;
+          const char *added_path;
+          const svn_string_t *added_path_parent_propval;
+
+          apr_hash_this(hi, &key, NULL, NULL);
+          added_path = key;
+
+          apr_pool_clear(iterpool);
+
+          /* Rather than using svn_client__get_wc_mergeinfo() and
+             analyzing the mergeinfo it returns to determine if
+             ADDED_PATH's parent has non-inheritable mergeinfo, it is
+             much simpler to just get the svn_string_t representation
+             of the svn:mergeinfo prop and look for the '*'
+             non-inheritable marker. */
+          SVN_ERR(svn_wc_prop_get(&added_path_parent_propval,
+                                  SVN_PROP_MERGEINFO,
+                                  svn_dirent_dirname(added_path, iterpool),
+                                  adm_access, iterpool));
+          if (added_path_parent_propval
+              && strstr(added_path_parent_propval->data,
+                        SVN_MERGEINFO_NONINHERITABLE_STR))
+            {
+              /* ADDED_PATH's immediate parent has non-inheritable
+                 mergeinfo. */
+              svn_client__merge_path_t *target_merge_path =
+                APR_ARRAY_IDX(notify_b->children_with_mergeinfo, 0,
+                              svn_client__merge_path_t *);
+              svn_boolean_t inherited;
+              svn_merge_range_t *rng;
+              svn_mergeinfo_t merge_mergeinfo, added_path_mergeinfo;
+              apr_array_header_t *rangelist;
+              const svn_wc_entry_t *entry;
+              const char *rel_added_path,
+                *abs_added_path, *abs_target_path,
+                *added_path_mergeinfo_path;
+
+              SVN_ERR(svn_wc__entry_versioned(&entry, added_path,
+                                              adm_access, FALSE,
+                                              iterpool));
+
+              /* Calculate the mergeinfo resulting from this merge. */
+              merge_mergeinfo = apr_hash_make(iterpool);
+              rangelist = apr_array_make(iterpool, 1,
+                                         sizeof(svn_merge_range_t *));
+              rng = svn_merge_range_dup(merged_range, iterpool);
+              if (entry->kind == svn_node_file)
+                rng->inheritable = TRUE;
+              else
+                rng->inheritable =
+                  (!(depth == svn_depth_infinity
+                     || depth == svn_depth_immediates));
+              APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = rng;
+
+              /* Create the new mergeinfo path for
+                 added_path's mergeinfo. */
+              SVN_ERR(svn_path_get_absolute(&abs_target_path,
+                                            target_merge_path->path,
+                                            iterpool));
+              SVN_ERR(svn_path_get_absolute(&abs_added_path,
+                                            added_path,
+                                            iterpool));
+
+              /* abs_added_path had better be a child of abs_target_path
+                 or something is *really* wrong. */
+
+              rel_added_path = svn_path_is_child(abs_target_path,
+                                                 abs_added_path,
+                                                 iterpool);
+              SVN_ERR_ASSERT(rel_added_path);
+              added_path_mergeinfo_path = svn_path_join(mergeinfo_path,
+                                                        rel_added_path,
+                                                        iterpool);
+              apr_hash_set(merge_mergeinfo,
+                           added_path_mergeinfo_path,
+                           APR_HASH_KEY_STRING, rangelist);
+
+              /* Get any explicit mergeinfo the added path has. */
+              SVN_ERR(svn_client__get_wc_mergeinfo(
+                &added_path_mergeinfo, &inherited, FALSE,
+                svn_mergeinfo_explicit, entry, added_path,
+                NULL, NULL, adm_access, merge_b->ctx, iterpool));
+
+              /* Combine the explict mergeinfo on the added path (if any)
+                 with the mergeinfo for this merge. */
+              if (added_path_mergeinfo)
+                SVN_ERR(svn_mergeinfo_merge(merge_mergeinfo,
+                                            added_path_mergeinfo,
+                                            iterpool));
+              SVN_ERR(svn_client__record_wc_mergeinfo(added_path,
+                                                      merge_mergeinfo,
+                                                      adm_access,
+                                                      iterpool));
+            }
+        }
+    }
+  return SVN_NO_ERROR;
+}
+
 /* Helper for do_merge() when the merge target is a directory.
 
    Perform a merge of changes between URL1@REVISION1 and URL2@REVISION2
@@ -6339,12 +6940,8 @@ do_directory_merge(const char *url1,
                    apr_pool_t *pool)
 {
   svn_error_t *err = SVN_NO_ERROR;
-  int merge_target_len = strlen(merge_b->target);
-  int i;
   svn_merge_range_t range;
   svn_ra_session_t *ra_session;
-  svn_boolean_t inheritable;
-  apr_pool_t *iterpool;
   const char *target_wcpath = svn_wc_adm_access_path(adm_access);
   svn_client__merge_path_t *target_merge_path;
   svn_boolean_t is_rollback = (revision1 > revision2);
@@ -6360,36 +6957,12 @@ do_directory_merge(const char *url1,
   notify_b->children_with_mergeinfo =
     apr_array_make(pool, 0, sizeof(svn_client__merge_path_t *));
 
-  /* If our merge sources aren't related to each other, or don't come
-     from the same repository as our target, mergeinfo is meaningless
-     and we can skip right to the business of merging changes!  We'll
-     just drop a dummy item into NOTIFY_B->CHILDREN_WITH_MERGEINFO if
-     the merge sources are related.  */
-  if (! (merge_b->sources_ancestral && merge_b->same_repos))
-    {
-      if (merge_b->sources_ancestral)
-        {
-          svn_client__merge_path_t *item = apr_pcalloc(pool, sizeof(*item));
-          svn_merge_range_t *itemrange = apr_pcalloc(pool, sizeof(*itemrange));
-          apr_array_header_t *remaining_ranges =
-            apr_array_make(pool, 1, sizeof(svn_merge_range_t *));
-
-          itemrange->start = revision1;
-          itemrange->end = revision2;
-          itemrange->inheritable = TRUE;
-          APR_ARRAY_PUSH(remaining_ranges, svn_merge_range_t *) = itemrange;
-
-          item->path = apr_pstrdup(pool, target_wcpath);
-          item->remaining_ranges = remaining_ranges;
-          APR_ARRAY_PUSH(notify_b->children_with_mergeinfo,
-                         svn_client__merge_path_t *) = item;
-        }
-      return drive_merge_report_editor(target_wcpath,
-                                       url1, revision1, url2, revision2,
-                                       NULL, depth, notify_b,
-                                       adm_access, &merge_callbacks,
-                                       merge_b, pool);
-    }
+  /* If we are not honoring mergeinfo we can skip right to the
+     business of merging changes! */
+  if (!honor_mergeinfo)
+    return do_mergeinfo_unaware_dir_merge(url1, revision1, url2, revision2,
+                                          target_wcpath, adm_access, depth,
+                                          notify_b, merge_b, pool);
 
   /*** If we get here, we're dealing with related sources from the
        same repository as the target -- merge tracking might be
@@ -6418,9 +6991,6 @@ do_directory_merge(const char *url1,
   target_merge_path = APR_ARRAY_IDX(notify_b->children_with_mergeinfo, 0,
                                     svn_client__merge_path_t *);
   merge_b->target_missing_child = target_merge_path->missing_child;
-  inheritable = ((! merge_b->target_missing_child)
-                 && ((depth == svn_depth_infinity)
-                     || (depth == svn_depth_immediates)));
 
   /* If we are honoring mergeinfo, then for each item in
      NOTIFY_B->CHILDREN_WITH_MERGEINFO, we need to calculate what needs to be
@@ -6429,15 +6999,15 @@ do_directory_merge(const char *url1,
   SVN_ERR(populate_remaining_ranges(notify_b->children_with_mergeinfo,
                                     source_root_url,
                                     url1, revision1, url2, revision2,
-                                    inheritable, honor_mergeinfo,
+                                    honor_mergeinfo,
                                     ra_session, mergeinfo_path,
-                                    adm_access, merge_b));
+                                    adm_access, merge_b, pool));
 
   /* Always start with a range which describes the most inclusive merge
      possible, i.e. REVISION1:REVISION2. */
   range.start = revision1;
   range.end = revision2;
-  range.inheritable = inheritable;
+  range.inheritable = TRUE;
 
   if (honor_mergeinfo && !merge_b->record_only)
     {
@@ -6455,6 +7025,8 @@ do_directory_merge(const char *url1,
       /* Is there anything to merge? */
       if (SVN_IS_VALID_REVNUM(start_rev))
         {
+          apr_pool_t *iterpool;
+
           /* Adjust range to describe the start of our most
              inclusive merge. */
           range.start = start_rev;
@@ -6563,8 +7135,7 @@ do_directory_merge(const char *url1,
                  inherited is recorded and then add these paths to
                  MERGE_B->CHILDREN_WITH_MERGEINFO.*/
               SVN_ERR(process_children_with_new_mergeinfo(merge_b, notify_b,
-                                                          adm_access,
-                                                          iterpool));
+                                                          adm_access, pool));
 
               /* If any subtrees had their explicit mergeinfo deleted as a
                  result of the merge then remove these paths from
@@ -6618,149 +7189,16 @@ do_directory_merge(const char *url1,
     }
 
   /* Record mergeinfo where appropriate.*/
-  iterpool = svn_pool_create(pool);
   if (record_mergeinfo)
     {
-      apr_array_header_t *filtered_rangelist;
-      svn_client__merge_path_t *merge_target =
-        APR_ARRAY_IDX(notify_b->children_with_mergeinfo, 0,
-                      svn_client__merge_path_t *);
-
-      /* Update the WC mergeinfo here to account for our new
-         merges, minus any unresolved conflicts and skips. */
-      apr_hash_t *merges;
-
-      /* Remove absent children at or under TARGET_WCPATH from
-         NOTIFY_B->SKIPPED_PATHS and NOTIFY_B->CHILDREN_WITH_MERGEINFO
-         before we calculate the merges performed. */
-      remove_absent_children(merge_b->target,
-                             notify_b->children_with_mergeinfo, notify_b);
-
-      /* Filter any ranges from MERGE_B->TARGET's own history, there is no
-         need to record this explicitly in mergeinfo, it is already part of
-         MERGE_B->TARGET's natural history (implicit mergeinfo). */
-      SVN_ERR(filter_natural_history_from_mergeinfo(
-        &filtered_rangelist, mergeinfo_path, merge_target->implicit_mergeinfo,
-        &range, iterpool));
-
-      if (filtered_rangelist->nelts)
-        {
-          SVN_ERR(determine_merges_performed(&merges, merge_b->target,
-                                             filtered_rangelist, depth,
-                                             adm_access, notify_b,
-                                             merge_b, iterpool));
-          SVN_ERR(update_wc_mergeinfo(merge_b->target, target_entry,
-                                      mergeinfo_path, merges,
-                                      is_rollback, adm_access, merge_b->ctx,
-                                      iterpool));
-        }
-
-      for (i = 0; i < notify_b->children_with_mergeinfo->nelts; i++)
-        {
-          const char *child_repos_path;
-          const char *child_merge_src_canon_path;
-          const svn_wc_entry_t *child_entry;
-          apr_array_header_t *child_merge_rangelist;
-          apr_hash_t *child_merges;
-          svn_client__merge_path_t *child =
-                         APR_ARRAY_IDX(notify_b->children_with_mergeinfo, i,
-                                       svn_client__merge_path_t *);
-          SVN_ERR_ASSERT(child);
-          if (child->absent)
-            continue;
-
-          svn_pool_clear(iterpool);
-
-          if (strlen(child->path) == merge_target_len)
-            child_repos_path = "";
-          else
-            child_repos_path = child->path +
-              (merge_target_len ? merge_target_len + 1 : 0);
-          child_merge_src_canon_path = svn_path_join(mergeinfo_path,
-                                                     child_repos_path,
-                                                     iterpool);
-          SVN_ERR(svn_wc__entry_versioned(&child_entry, child->path,
-                                          adm_access, FALSE, iterpool));
-
-          child_merges = apr_hash_make(iterpool);
-
-          /* As we did above for the merge target, filter any ranges from
-             each child's natural history before setting mergeinfo. */
-          SVN_ERR(filter_natural_history_from_mergeinfo(
-            &child_merge_rangelist, child_merge_src_canon_path,
-            child->implicit_mergeinfo, &range, iterpool));
-
-          if (child_merge_rangelist->nelts == 0)
-            continue;
-          else
-            {
-              int j;
-              for (j = 0; j < child_merge_rangelist->nelts; j++)
-                {
-                  svn_merge_range_t *rng =
-                    APR_ARRAY_IDX(child_merge_rangelist, j,
-                                  svn_merge_range_t *);
-                  if (child_entry->kind == svn_node_file)
-                    rng->inheritable = TRUE;
-                  else
-                    rng->inheritable = (!(child->missing_child)
-                                        && (depth == svn_depth_infinity
-                                     || depth == svn_depth_immediates));
-                }
-            }
-          apr_hash_set(child_merges, child->path, APR_HASH_KEY_STRING,
-                       child_merge_rangelist);
-          /* If merge target has indirect mergeinfo set it before
-             recording the first merge range. */
-          if (child->indirect_mergeinfo)
-            {
-              SVN_ERR(svn_client__record_wc_mergeinfo(
-                                               child->path,
-                                               child->pre_merge_mergeinfo,
-                                               adm_access,
-                                               iterpool));
-            }
-          SVN_ERR(update_wc_mergeinfo(child->path, child_entry,
-                                      child_merge_src_canon_path,
-                                      child_merges, is_rollback,
-                                      adm_access, merge_b->ctx, iterpool));
-
-          /* Elide explicit subtree mergeinfo. */
-          if (i > 0)
-            {
-              svn_boolean_t in_switched_subtree = FALSE;
-
-              if (child->switched)
-                in_switched_subtree = TRUE;
-              else if (i > 1)
-                {
-                  /* Check if CHILD is part of a switched subtree */
-                  svn_client__merge_path_t *parent;
-                  int j = i - 1;
-                  for (; j > 0; j--)
-                    {
-                      parent = APR_ARRAY_IDX(notify_b->children_with_mergeinfo,
-                                             j, svn_client__merge_path_t *);
-                      if (parent
-                          && parent->switched
-                          && svn_path_is_ancestor(parent->path, child->path))
-                        {
-                          in_switched_subtree = TRUE;
-                          break;
-                        }
-                    }
-                }
-
-              /* Allow mergeinfo on switched subtrees to elide to the
-                 repository. Otherwise limit elision to the merge target
-                 for now.  do_directory_merge() will eventually try to
-                 elide that when the merge is complete. */
-              SVN_ERR(svn_client__elide_mergeinfo(
-                child->path,
-                in_switched_subtree ? NULL : merge_b->target,
-                child_entry, adm_access, merge_b->ctx, iterpool));
-            }
-        } /* (i = 0; i < notify_b->children_with_mergeinfo->nelts; i++) */
+      SVN_ERR(record_mergeinfo_for_dir_merge(target_entry,
+                                             &range,
+                                             mergeinfo_path,
+                                             depth,
+                                             notify_b,
+                                             merge_b,
+                                             adm_access,
+                                             pool));
 
       /* If a path has an immediate parent with non-inheritable mergeinfo at
          this point, then it meets criteria 3 or 5 described in
@@ -6776,109 +7214,15 @@ do_directory_merge(const char *url1,
          So here we look at the root path of each subtree added during the
          merge and set explicit mergeinfo on it if it meets the aforementioned
          conditions. */
-      if (notify_b->added_paths)
-        {
-          apr_hash_index_t *hi;
-
-          for (hi = apr_hash_first(NULL, notify_b->added_paths); hi;
-               hi = apr_hash_next(hi))
-            {
-              const void *key;
-              const char *added_path;
-              const svn_string_t *added_path_parent_propval;
-
-              apr_hash_this(hi, &key, NULL, NULL);
-              added_path = key;
-
-              apr_pool_clear(iterpool);
-
-              /* Rather than using svn_client__get_wc_mergeinfo() and
-                 analyzing the mergeinfo it returns to determine if
-                 ADDED_PATH's parent has non-inheritable mergeinfo, it is
-                 much simpler to just get the svn_string_t representation
-                 of the svn:mergeinfo prop and look for the '*'
-                 non-inheritable marker. */
-              SVN_ERR(svn_wc_prop_get(&added_path_parent_propval,
-                                      SVN_PROP_MERGEINFO,
-                                      svn_path_dirname(added_path, iterpool),
-                                      adm_access, iterpool));
-              if (added_path_parent_propval
-                  && strstr(added_path_parent_propval->data,
-                            SVN_MERGEINFO_NONINHERITABLE_STR))
-                {
-                  /* ADDED_PATH's immediate parent has non-inheritable
-                     mergeinfo. */
-                  svn_boolean_t inherited;
-                  svn_merge_range_t *rng;
-                  svn_mergeinfo_t merge_mergeinfo, added_path_mergeinfo;
-                  apr_array_header_t *rangelist;
-                  const svn_wc_entry_t *entry;
-                  const char *rel_added_path,
-                    *abs_added_path, *abs_target_path,
-                    *added_path_mergeinfo_path;
-
-                  SVN_ERR(svn_wc__entry_versioned(&entry, added_path,
-                                                  adm_access, FALSE,
-                                                  iterpool));
-
-                  /* Calculate the mergeinfo resulting from this merge. */
-                  merge_mergeinfo = apr_hash_make(iterpool);
-                  rangelist = apr_array_make(iterpool, 1,
-                                             sizeof(svn_merge_range_t *));
-                  rng = svn_merge_range_dup(&range, iterpool);
-                  if (entry->kind == svn_node_file)
-                    rng->inheritable = TRUE;
-                  else
-                    rng->inheritable =
-                      (!(depth == svn_depth_infinity
-                         || depth == svn_depth_immediates));
-                  APR_ARRAY_PUSH(rangelist, svn_merge_range_t *) = rng;
-
-                  /* Create the new mergeinfo path for
-                     added_path's mergeinfo. */
-                  SVN_ERR(svn_path_get_absolute(&abs_target_path,
-                                                target_merge_path->path,
-                                                iterpool));
-                  SVN_ERR(svn_path_get_absolute(&abs_added_path,
-                                                added_path,
-                                                iterpool));
-
-                  /* abs_added_path had better be a child of abs_target_path
-                     or something is *really* wrong. */
-
-                  rel_added_path = svn_path_is_child(abs_target_path,
-                                                     abs_added_path,
-                                                     iterpool);
-                  SVN_ERR_ASSERT(rel_added_path);
-                  added_path_mergeinfo_path = svn_path_join(mergeinfo_path,
-                                                            rel_added_path,
-                                                            iterpool);
-                  apr_hash_set(merge_mergeinfo,
-                               added_path_mergeinfo_path,
-                               APR_HASH_KEY_STRING, rangelist);
-
-                  /* Get any explicit mergeinfo the added path has. */
-                  SVN_ERR(svn_client__get_wc_mergeinfo(
-                    &added_path_mergeinfo, &inherited, FALSE,
-                    svn_mergeinfo_explicit, entry, added_path,
-                    NULL, NULL, adm_access, merge_b->ctx, iterpool));
-
-                  /* Combine the explict mergeinfo on the added path (if any)
-                     with the mergeinfo for this merge. */
-                  if (added_path_mergeinfo)
-                    SVN_ERR(svn_mergeinfo_merge(merge_mergeinfo,
-                                                added_path_mergeinfo,
-                                                iterpool));
-                  SVN_ERR(svn_client__record_wc_mergeinfo(added_path,
-                                                          merge_mergeinfo,
-                                                          adm_access,
-                                                          iterpool));
-                }
-            }
-        }
-    } /* (!merge_b->dry_run && merge_b->same_repos) */
+      SVN_ERR(record_mergeinfo_for_added_subtrees(&range,
+                                                  mergeinfo_path,
+                                                  depth,
+                                                  notify_b,
+                                                  merge_b,
+                                                  adm_access,
+                                                  pool));
+    }
 
-  svn_pool_destroy(iterpool);
   return err;
 }
 
@@ -7066,6 +7410,7 @@ do_merge(apr_array_header_t *merge_sourc
       /* Populate the portions of the merge context baton that need to
          be reset for each merge source iteration. */
       merge_cmd_baton.merge_source = *merge_source;
+      merge_cmd_baton.implicit_src_gap = NULL;
       merge_cmd_baton.added_path = NULL;
       merge_cmd_baton.add_necessitated_merge = FALSE;
       merge_cmd_baton.dry_run_deletions =

Modified: subversion/branches/1.6.x/subversion/libsvn_client/mergeinfo.h
URL: http://svn.apache.org/viewvc/subversion/branches/1.6.x/subversion/libsvn_client/mergeinfo.h?rev=924028&r1=924027&r2=924028&view=diff
==============================================================================
--- subversion/branches/1.6.x/subversion/libsvn_client/mergeinfo.h (original)
+++ subversion/branches/1.6.x/subversion/libsvn_client/mergeinfo.h Tue Mar 16 22:27:36 2010
@@ -52,7 +52,7 @@ typedef struct svn_client__merge_path_t
      svn_sort_compare_ranges(), but rather are sorted such that the ranges
      with the youngest start revisions come first.  In both the forward and
      reverse merge cases the ranges should never overlap.  This rangelist
-     may be NULL or empty. */
+     may be empty but should never be NULL. */
   apr_array_header_t *remaining_ranges;
 
   svn_mergeinfo_t pre_merge_mergeinfo;  /* Mergeinfo on PATH prior to a

Modified: subversion/branches/1.6.x/subversion/libsvn_subr/mergeinfo.c
URL: http://svn.apache.org/viewvc/subversion/branches/1.6.x/subversion/libsvn_subr/mergeinfo.c?rev=924028&r1=924027&r2=924028&view=diff
==============================================================================
--- subversion/branches/1.6.x/subversion/libsvn_subr/mergeinfo.c (original)
+++ subversion/branches/1.6.x/subversion/libsvn_subr/mergeinfo.c Tue Mar 16 22:27:36 2010
@@ -817,6 +817,24 @@ svn_rangelist_reverse(apr_array_header_t
   return SVN_NO_ERROR;
 }
 
+void
+svn_rangelist__set_inheritance(apr_array_header_t *rangelist,
+                               svn_boolean_t inheritable)
+{
+  if (rangelist)
+    {
+      int i;
+      svn_merge_range_t *range;
+
+      for (i = 0; i < rangelist->nelts; i++)
+        {
+          range = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
+          range->inheritable = inheritable;
+        }
+    }
+  return;
+}
+
 /* Either remove any overlapping ranges described by ERASER from
    WHITEBOARD (when DO_REMOVE is TRUE), or capture the overlap, and
    place the remaining or overlapping ranges in OUTPUT. */



Mime
View raw message