You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@subversion.apache.org by Julian Foad <ju...@btopenworld.com> on 2012/07/31 19:50:12 UTC

Symmetric merge and deleted subtrees

I'm investigating a discrepancy that shows up, when 'symmetric merge' is enabled [1], in the notifications printed by some merges.  Merge_tests 78 fails because the expected notification for the last merge is:

--- Merging r6 through r9 into 'H_COPY':
U    H_COPY/psi
D    H_COPY/nu
--- Recording mergeinfo for merge of r6 through r9 into 'H_COPY':
 U   H_COPY

but the first line of that changes to:

--- Merging r7 through r9 into 'H_COPY':

while the rest stays the same.  In this instance the difference is probably harmless since r6 is a no-op in the merge source, but we need to check whether this is a symptom of a wider problem.


NEW TEST FOR THIS ISSUE

I have simplified the recipe into a new test, merge_tests-130 in 
the attached patch [2].  I removed the '--force' option, and this changes things such that the merge prints a separate notification for the subtree.  Also 
in that patch are some debug prints in the merge code which help to 
trace the source of the problem.  A diagram of the test scenario:

  #                        merge -c4 A/D/H/nu@4 H_COPY/nu
  #                        |  merge A/D/H H_COPY
  #                        |  |
  # A/D/H      A---------------
  #     +-psi  +---------M-----
  #     +-nu     A---M-D
  # H_COPY         C----------G
  #     +-psi      +----------+
  #     +-nu       +-------G--+
  #            1 2 3 4 5 6 wc wc
  #
  # Key: Add Mod Del Copy merGe

The difference in outputs ('-': existing 1.7 merge, '+': symmetric merge):

 I: CMD: svn merge ^/A/D/H H_COPY
 I: DBG: merge.c:8568: 1: ...130/H_COPY: 3-6
 I: DBG: merge.c:8568: 1: .../H_COPY/nu: 3,5-6
 I: DBG: merge.c:8568: 2: ...130/H_COPY: 3-6
 I: DBG: merge.c:8568: 2: .../H_COPY/nu: 3,5-6
 I: DBG: merge.c:8568: 3: ...130/H_COPY: 3-6
-I: DBG: merge.c:8568: 3: .../H_COPY/nu: 3-6
+I: DBG: merge.c:8568: 3: .../H_COPY/nu: 3,5-6
-I: --- Merging r3 through r6 into 'H_COPY':
+I: --- Merging r4 through r6 into 'H_COPY':
 I: U    H_COPY/psi
-I: --- Merging r3 through r6 into 'H_COPY/nu':
+I: --- Merging r5 through r6 into 'H_COPY/nu':
 I:    C H_COPY/nu
 I: --- Recording mergeinfo for merge of r3 through r6 into 'H_COPY':
 I:  U   H_COPY
 I: --- Recording mergeinfo for merge of r3 through r6 into 'H_COPY/nu':
 I:  G   H_COPY/nu
 I: --- Eliding mergeinfo from 'H_COPY/nu':
 I:  U   H_COPY/nu
 I: Summary of conflicts:
 I:   Tree conflicts: 1

The immediate cause of the discrepancy is that the symmetric merge code 
passes the YCA revision as the beginning of the range to be merged, 
whereas the existing merge code passes revision '1' as the beginning of 
the range to be merged.  At the beginning of the main loop in do_merge():

- source = {loc1 = "/A/D/H@1", loc2 = "/A/D/H@6", ancestral = TRUE}
+ source = {loc1 = "/A/D/H@2", loc2 = "/A/D/H@6", ancestral = TRUE}

The underlying problem -- the reason why the merge code doesn't resolve these two requests to the same result -- is deeper, involving the function fix_deleted_subtree_ranges().  That function yields

  (child 'H_COPY/nu')->remaining_ranges = 3-6

for the 1.7 merge and

  (child 'H_COPY/nu')->remaining_ranges = 3,5-6

for the symmetric merge.

Revision 4 has already been merged to 'H_COPY/nu', so it should not be merged again; but that doesn't mean 'remaining_ranges = 3-6' is wrong.  fix_deleted_subtree_ranges() is designed to set a subtree's remaining range to match its parent's remaining range if the subtree doesn't need a separate editor drive.  The function appears to be concluding in the first case that the subtree doesn't need a separate editor drive (since, over the range 3-6, the net result is simply to delete the subtree), but in the second case that it does and should be split into two sub-ranges.

I'm not sure whether one of those conclusions is right and the other wrong; maybe both are OK.  I'm not sure why there are two different conclusions.


TREE CONFLICT

Another issue I have identified here is that, in either case, a tree conflict is flagged unnecessarily on 'nu'.  (That doesn't happen in merge_tests-78 because that test passes the '--force' flag.)

In the 1.7 case, the merge reports that it is merging 'r3 through r6', and the conflict details are reported as:

  $ svn info H_COPY/nu
  Tree conflict: local edit, incoming delete upon merge
    Source  left: (file) ^/A/D/H/nu@1
    Source right: (file) ^/A/D/H/nu@6

First, we notice that the reported 'left' and 'right' details don't match up with the description as an incoming 'delete'.  I recall that's a long-standing error in the conflict reporting.

The working file to be deleted ('H_COPY/nu' with r4 merged in to it) has content that doesn't match the expected pre-delete content (A/D/H/nu@2, because the revs to be merged are 3-6).  I haven't checked the detection code yet, but if that's why the conflict is flagged, then clearly the tree-conflict logic that says 'the content should match that at the beginning of the range being merged' is incompatible with the logic of fix_deleted_subtree_ranges() which says 'there is no need to calculate the exact ranges to be merged if the end result is just a delete'.

In the symmetric merge case, the merge reports that it is merging 'r5 through r6', and the conflict details are:

  Tree conflict: local edit, incoming delete upon merge
    Source  left: (file) ^/A/D/H/nu@2
    Source right: (file) ^/A/D/H/nu@6

I don't know why that's flagging a conflict there -- do we flag a conflict just because it has local mod, regardless that it may have the expected content?


CONCLUSIONS SO FAR


  * There's a deficiency in fix_deleted_subtree_ranges() -- it should at least give consistent results with these two different input ranges.

  * As a temporary or diagnostic measure we might be able to make 'symmetric merge' give the same result by making it pass the oldest possible revision (1) instead of the YCA revision (2) as the beginning of the range to consider.

  * There's a deficiency in conflict detection.


I will continue to investigate all of this, but any assistance is very welcome.

- Julian

[1] Patch to enable symmetric merge: attachment 'use-symmetric-merge-1.patch'.

[2] New test and debug prints: attachment 'deleted-subtree-ranges-1.patch'.



--
Certified & Supported Apache Subversion Downloads: http://www.wandisco.com/subversion/download

Re: Symmetric merge and deleted subtrees

Posted by Julian Foad <ju...@btopenworld.com>.
I (Julian Foad) wrote:

> I have only had a chance to skim through your reply so far.  I'm glad that 
> you find it to be an explicable and harmless difference.

OK, I follow your explanation and agree; thanks.  I have closed the isse #4217.

In investigating this, I tried your suggestion of doing the 'remove_noop_subtree_ranges' after the 'fix_deleted_subtree_ranges'.  It triggered an assertion, one that I added recently, checking that the session URL was unchanged; this led me to find a bug I introduced (I think) whereby in several functions we reparent an RA session 'temporarily' but we don't take care to restore the original session URL if an error occurs.

On fixing that, I find that moving this code makes no difference to the editor drives made by this test.  I haven't looked further into eliminating the no-op drive.

I do wonder, though, if instead of trying to detect and eliminate revision ranges here that would cause an 'invalid editor drive' later, it might be simpler instead to change the 'do an editor drive' subtroutine to handle such cases (where the start and/or end revision of the range doesn't exist).  I will look into that some time.

- Julian


> I have assigned Issue #4217 to track this, as I want to be able to refer to it 
> before having a chance to study your reply properly.
> 
> - Julian

Re: Symmetric merge and deleted subtrees

Posted by Julian Foad <ju...@btopenworld.com>.
Hi Paul.


I have only had a chance to skim through your reply so far.  I'm glad that you find it to be an explicable and harmless difference.

I have assigned Issue #4217 to track this, as I want to be able to refer to it before having a chance to study your reply properly.

- Julian


Paul Burba wrote:
> On Tue, Jul 31, 2012 at 1:50 PM, Julian Foad wrote:
>>  I'm investigating a discrepancy that shows up, when 'symmetric 
>> merge' is enabled [1], in the notifications printed by some merges.  
>> Merge_tests 78 fails because the expected notification for the last
>> merge is:
>> 
>>  --- Merging r6 through r9 into 'H_COPY':
>>  U    H_COPY/psi
>>  D    H_COPY/nu
>>  --- Recording mergeinfo for merge of r6 through r9 into 'H_COPY':
>>   U   H_COPY
>> 
>>  but the first line of that changes to:
>> 
>>  --- Merging r7 through r9 into 'H_COPY':
>> 
>>  while the rest stays the same.  In this instance the difference is
>> probably harmless since r6 is a no-op in the merge source, but we
>> need to check whether this is a symptom of a wider problem.
>> 
>> 
>>  NEW TEST FOR THIS ISSUE
>> 
>>  I have simplified the recipe into a new test, merge_tests-130 in
>>  the attached patch [2].  I removed the '--force' option, and this
>> changes things such that the merge prints a separate notification
>> for the  subtree.  Also
>>  in that patch are some debug prints in the merge code which help to
>>  trace the source of the problem.  A diagram of the test scenario:
>> 
>>    #                        merge -c4 A/D/H/nu@4 H_COPY/nu
>>    #                        |  merge A/D/H H_COPY
>>    #                        |  |
>>    # A/D/H      A---------------
>>    #     +-psi  +---------M-----
>>    #     +-nu     A---M-D
>>    # H_COPY         C----------G
>>    #     +-psi      +----------+
>>    #     +-nu       +-------G--+
>>    #            1 2 3 4 5 6 wc wc
>>    #
>>    # Key: Add Mod Del Copy merGe
>> 
>>  The difference in outputs ('-': existing 1.7 merge, '+': 
>> symmetric merge):
>> 
>>   I: CMD: svn merge ^/A/D/H H_COPY
>>   I: DBG: merge.c:8568: 1: ...130/H_COPY: 3-6
>>   I: DBG: merge.c:8568: 1: .../H_COPY/nu: 3,5-6
>>   I: DBG: merge.c:8568: 2: ...130/H_COPY: 3-6
>>   I: DBG: merge.c:8568: 2: .../H_COPY/nu: 3,5-6
>>   I: DBG: merge.c:8568: 3: ...130/H_COPY: 3-6
>>  -I: DBG: merge.c:8568: 3: .../H_COPY/nu: 3-6
>>  +I: DBG: merge.c:8568: 3: .../H_COPY/nu: 3,5-6
>>  -I: --- Merging r3 through r6 into 'H_COPY':
>>  +I: --- Merging r4 through r6 into 'H_COPY':
>>   I: U    H_COPY/psi
>>  -I: --- Merging r3 through r6 into 'H_COPY/nu':
>>  +I: --- Merging r5 through r6 into 'H_COPY/nu':
>>   I:    C H_COPY/nu
>>   I: --- Recording mergeinfo for merge of r3 through r6 into 
>> 'H_COPY':
>>   I:  U   H_COPY
>>   I: --- Recording mergeinfo for merge of r3 through r6 into 
>> 'H_COPY/nu':
>>   I:  G   H_COPY/nu
>>   I: --- Eliding mergeinfo from 'H_COPY/nu':
>>   I:  U   H_COPY/nu
>>   I: Summary of conflicts:
>>   I:   Tree conflicts: 1
>> 
>>  The immediate cause of the discrepancy is that the symmetric merge code
>>  passes the YCA revision as the beginning of the range to be merged,
>>  whereas the existing merge code passes revision '1' as the
>>  beginning of
>>  the range to be merged.  At the beginning of the main loop in do_merge():
>> 
>>  - source = {loc1 = "/A/D/H@1", loc2 = "/A/D/H@6", 
>> ancestral = TRUE}
>>  + source = {loc1 = "/A/D/H@2", loc2 = "/A/D/H@6", 
>> ancestral = TRUE}
>> 
>>  The underlying problem -- the reason why the merge code doesn't resolve
>> these two requests to the same result -- is deeper, involving the
>> function fix_deleted_subtree_ranges().  That function yields
>> 
>>    (child 'H_COPY/nu')->remaining_ranges = 3-6
>> 
>>  for the 1.7 merge and
>> 
>>    (child 'H_COPY/nu')->remaining_ranges = 3,5-6
>> 
>>  for the symmetric merge.
>> 
>>  Revision 4 has already been merged to 'H_COPY/nu', so it should not
>> be merged again; but that doesn't mean 'remaining_ranges = 3-6' is
>> wrong.  fix_deleted_subtree_ranges() is designed to set a subtree's
>> remaining range to match its parent's remaining range if the subtree
>> doesn't need a separate editor drive.  The function appears to be
>> concluding in the first case that the subtree doesn't need a separate
>> editor drive  (since, over the range 3-6, the net result is simply to
>> delete the subtree), but in the second case that it does and should
>> be split into two sub-ranges.
> 
> Hi Julian,
> 
> I don't think there is a problem here, rather, as you point out above,
> it is a fundamental difference between 1.7 sync merges, which assume a
> starting rev of 1, and symmetric merges, which calculates the YCA and
> passes that as the starting rev to do_merge().  Different inputs to a
> function produce different outputs...nothing terribly shocking about
> that ;-)
> 
> Specifically:
> 
> When we reach fix_deleted_subtree_ranges we are asking different
> questions during a 1.7 merge vs. a symmetric merge.
> 
> First let's look at a couple key parts of the doc string for
> adjust_deleted_subtree_ranges (which does the heavy lifting for
> fix_deleted_subtree_ranges), first:
> 
>    Since this function is only invoked for subtrees of the merge target, the
>    guarantees afforded by normalize_merge_sources() don't apply - see the
>    'MERGEINFO MERGE SOURCE NORMALIZATION' comment at the top of this
>    file.  Therefore it is possible that PRIMARY_URL@REVISION1 and
>    PRIMARY_URL@REVISION2 don't describe the endpoints of an unbroken line of
>    history.  The purpose of this helper is to identify these cases of broken
>    history and adjust CHILD->REMAINING_RANGES in such a way we don't later
>    try to describe nonexistent path/revisions to the merge report editor --
>    see drive_merge_report_editor().
> 
> So, we are trying to not break the editor drive.  In the 1.7 case we
> ask adjust_deleted_subtree_ranges about this child:
> 
> adjust_deleted_subtree_ranges(svn_client__merge_path_t *child = 
> "H_COPY/nu"...,
>                               svn_client__merge_path_t *parent = 
> "H_COPY"...,
>                               svn_revnum_t revision1 = 1,
>                               svn_revnum_t revision2 = 6,
>                               const char *primary_url = "^/A/D/H/nu",
> 
> *but* neither ^/A/D/H/nu@1 or ^/A/D/H/nu@6 exist, so CHILD needs no
> special consideration during the editor drive, so we set the CHILD's
> remaining ranges as identical to it's parents.  As the doc string for
> drive_merge_report_editor points out, this means we effectively ignore
> these children during the editor drive:
> 
>    ...
>    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 nearest path-wise ancestor, then the subtree child
>    *will not* be described to the reporter.
>    ...
> 
> The symmetric merge however, is asking about a CHILD that exists at
> the start of the requested range, but not at the end (it is deleted):
> 
> adjust_deleted_subtree_ranges(svn_client__merge_path_t *child =
> "H_COPY/nu"...,
>                               svn_client__merge_path_t *parent =
> "H_COPY"...,
>                               svn_revnum_t revision1 = 2,
>                               svn_revnum_t revision2 = 6,
>                               const char *primary_url = "^/A/D/H/nu",
> 
> So per the guarantees of adjust_deleted_subtree_ranges we end up with
> 2 discrete remaining ranges for H_COPY/nu:
> 
>      If PRIMARY_URL@REVISION1 exists but PRIMARY_URL@REVISION2 doesn't,
>      then find the revision 'N' in which PRIMARY_URL@REVISION1 was
>      deleted.  Leave the subset of CHILD->REMAINING_RANGES that
>      intersects with REVISION1:(N - 1) as-is and set the subset of
>      CHILD->REMAINING_RANGES that intersects with (N - 1):REVISION2 equal
>      to PARENT->REMAINING_RANGES' intersection with (N - 1):REVISION2.
> 
>>  I'm not sure whether one of those conclusions is right and the other
>> wrong; maybe both are OK.
> 
> I think everything is ok here, at least as far as the resulting merge,
> we should always get the same WC state AFAICT with either the
> symmetric merge or the 1.7 merge.
> 
> The only drawback to the symmetric approach is that it performs two
> editor drives:
> 
>   Editor Drive #1: ^/merge_tests-130 rev=2 : ^/merge_tests-130 rev=3
>   Editor Drive #2: ^/merge_tests-130 rev=3 : ^/merge_tests-130 rev=6
> 
> The first is inoperative obviously, so there is no user visible output.
> 
> While 1.7 performs only one:
> 
>   Editor Drive #1: ^/merge_tests-130 rev=2 : ^/merge_tests-130 rev=6
> 
> Not sure how to avoid this extra editor drive.  Possibly postponing
> the call to remove_noop_subtree_ranges() in
> do_mergeinfo_aware_dir_merge until *after* the call to
> fix_deleted_subtree_ranges() is all that is needed, but I haven't
> checked this out.
> 
>>  I'm not sure why there are two different conclusions.
> 
> Again, different conclusions based on different inputs.
> 
> -- 
> Paul T. Burba

Re: Symmetric merge and deleted subtrees

Posted by Paul Burba <pt...@gmail.com>.
On Tue, Jul 31, 2012 at 1:50 PM, Julian Foad <ju...@btopenworld.com> wrote:
> I'm investigating a discrepancy that shows up, when 'symmetric merge' is enabled [1], in the notifications printed by some merges.  Merge_tests 78 fails because the expected notification for the last merge is:
>
> --- Merging r6 through r9 into 'H_COPY':
> U    H_COPY/psi
> D    H_COPY/nu
> --- Recording mergeinfo for merge of r6 through r9 into 'H_COPY':
>  U   H_COPY
>
> but the first line of that changes to:
>
> --- Merging r7 through r9 into 'H_COPY':
>
> while the rest stays the same.  In this instance the difference is probably harmless since r6 is a no-op in the merge source, but we need to check whether this is a symptom of a wider problem.
>
>
> NEW TEST FOR THIS ISSUE
>
> I have simplified the recipe into a new test, merge_tests-130 in
> the attached patch [2].  I removed the '--force' option, and this changes things such that the merge prints a separate notification for the subtree.  Also
> in that patch are some debug prints in the merge code which help to
> trace the source of the problem.  A diagram of the test scenario:
>
>   #                        merge -c4 A/D/H/nu@4 H_COPY/nu
>   #                        |  merge A/D/H H_COPY
>   #                        |  |
>   # A/D/H      A---------------
>   #     +-psi  +---------M-----
>   #     +-nu     A---M-D
>   # H_COPY         C----------G
>   #     +-psi      +----------+
>   #     +-nu       +-------G--+
>   #            1 2 3 4 5 6 wc wc
>   #
>   # Key: Add Mod Del Copy merGe
>
> The difference in outputs ('-': existing 1.7 merge, '+': symmetric merge):
>
>  I: CMD: svn merge ^/A/D/H H_COPY
>  I: DBG: merge.c:8568: 1: ...130/H_COPY: 3-6
>  I: DBG: merge.c:8568: 1: .../H_COPY/nu: 3,5-6
>  I: DBG: merge.c:8568: 2: ...130/H_COPY: 3-6
>  I: DBG: merge.c:8568: 2: .../H_COPY/nu: 3,5-6
>  I: DBG: merge.c:8568: 3: ...130/H_COPY: 3-6
> -I: DBG: merge.c:8568: 3: .../H_COPY/nu: 3-6
> +I: DBG: merge.c:8568: 3: .../H_COPY/nu: 3,5-6
> -I: --- Merging r3 through r6 into 'H_COPY':
> +I: --- Merging r4 through r6 into 'H_COPY':
>  I: U    H_COPY/psi
> -I: --- Merging r3 through r6 into 'H_COPY/nu':
> +I: --- Merging r5 through r6 into 'H_COPY/nu':
>  I:    C H_COPY/nu
>  I: --- Recording mergeinfo for merge of r3 through r6 into 'H_COPY':
>  I:  U   H_COPY
>  I: --- Recording mergeinfo for merge of r3 through r6 into 'H_COPY/nu':
>  I:  G   H_COPY/nu
>  I: --- Eliding mergeinfo from 'H_COPY/nu':
>  I:  U   H_COPY/nu
>  I: Summary of conflicts:
>  I:   Tree conflicts: 1
>
> The immediate cause of the discrepancy is that the symmetric merge code
> passes the YCA revision as the beginning of the range to be merged,
> whereas the existing merge code passes revision '1' as the beginning of
> the range to be merged.  At the beginning of the main loop in do_merge():
>
> - source = {loc1 = "/A/D/H@1", loc2 = "/A/D/H@6", ancestral = TRUE}
> + source = {loc1 = "/A/D/H@2", loc2 = "/A/D/H@6", ancestral = TRUE}
>
> The underlying problem -- the reason why the merge code doesn't resolve these two requests to the same result -- is deeper, involving the function fix_deleted_subtree_ranges().  That function yields
>
>   (child 'H_COPY/nu')->remaining_ranges = 3-6
>
> for the 1.7 merge and
>
>   (child 'H_COPY/nu')->remaining_ranges = 3,5-6
>
> for the symmetric merge.
>
> Revision 4 has already been merged to 'H_COPY/nu', so it should not be merged again; but that doesn't mean 'remaining_ranges = 3-6' is wrong.  fix_deleted_subtree_ranges() is designed to set a subtree's remaining range to match its parent's remaining range if the subtree doesn't need a separate editor drive.  The function appears to be concluding in the first case that the subtree doesn't need a separate editor drive (since, over the range 3-6, the net result is simply to delete the subtree), but in the second case that it does and should be split into two sub-ranges.

Hi Julian,

I don't think there is a problem here, rather, as you point out above,
it is a fundamental difference between 1.7 sync merges, which assume a
starting rev of 1, and symmetric merges, which calculates the YCA and
passes that as the starting rev to do_merge().  Different inputs to a
function produce different outputs...nothing terribly shocking about
that ;-)

Specifically:

When we reach fix_deleted_subtree_ranges we are asking different
questions during a 1.7 merge vs. a symmetric merge.

First let's look at a couple key parts of the doc string for
adjust_deleted_subtree_ranges (which does the heavy lifting for
fix_deleted_subtree_ranges), first:

   Since this function is only invoked for subtrees of the merge target, the
   guarantees afforded by normalize_merge_sources() don't apply - see the
   'MERGEINFO MERGE SOURCE NORMALIZATION' comment at the top of this file.
   Therefore it is possible that PRIMARY_URL@REVISION1 and
   PRIMARY_URL@REVISION2 don't describe the endpoints of an unbroken line of
   history.  The purpose of this helper is to identify these cases of broken
   history and adjust CHILD->REMAINING_RANGES in such a way we don't later try
   to describe nonexistent path/revisions to the merge report editor -- see
   drive_merge_report_editor().

So, we are trying to not break the editor drive.  In the 1.7 case we
ask adjust_deleted_subtree_ranges about this child:

adjust_deleted_subtree_ranges(svn_client__merge_path_t *child = "H_COPY/nu"...,
                              svn_client__merge_path_t *parent = "H_COPY"...,
                              svn_revnum_t revision1 = 1,
                              svn_revnum_t revision2 = 6,
                              const char *primary_url = "^/A/D/H/nu",

*but* neither ^/A/D/H/nu@1 or ^/A/D/H/nu@6 exist, so CHILD needs no
special consideration during the editor drive, so we set the CHILD's
remaining ranges as identical to it's parents.  As the doc string for
drive_merge_report_editor points out, this means we effectively ignore
these children during the editor drive:

   ...
   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
   nearest path-wise ancestor, then the subtree child *will not* be described
   to the reporter.
   ...

The symmetric merge however, is asking about a CHILD that exists at
the start of the requested range, but not at the end (it is deleted):

adjust_deleted_subtree_ranges(svn_client__merge_path_t *child = "H_COPY/nu"...,
                              svn_client__merge_path_t *parent = "H_COPY"...,
                              svn_revnum_t revision1 = 2,
                              svn_revnum_t revision2 = 6,
                              const char *primary_url = "^/A/D/H/nu",

So per the guarantees of adjust_deleted_subtree_ranges we end up with
2 discrete remaining ranges for H_COPY/nu:

     If PRIMARY_URL@REVISION1 exists but PRIMARY_URL@REVISION2 doesn't, then
     find the revision 'N' in which PRIMARY_URL@REVISION1 was deleted.  Leave
     the subset of CHILD->REMAINING_RANGES that intersects with
     REVISION1:(N - 1) as-is and set the subset of CHILD->REMAINING_RANGES
     that intersects with (N - 1):REVISION2 equal to PARENT->REMAINING_RANGES'
     intersection with (N - 1):REVISION2.

> I'm not sure whether one of those conclusions is right and the other wrong; maybe both are OK.

I think everything is ok here, at least as far as the resulting merge,
we should always get the same WC state AFAICT with either the
symmetric merge or the 1.7 merge.

The only drawback to the symmetric approach is that it performs two
editor drives:

  Editor Drive #1: ^/merge_tests-130 rev=2 : ^/merge_tests-130 rev=3
  Editor Drive #2: ^/merge_tests-130 rev=3 : ^/merge_tests-130 rev=6

The first is inoperative obviously, so there is no user visible output.

While 1.7 performs only one:

  Editor Drive #1: ^/merge_tests-130 rev=2 : ^/merge_tests-130 rev=6

Not sure how to avoid this extra editor drive.  Possibly postponing
the call to remove_noop_subtree_ranges() in
do_mergeinfo_aware_dir_merge until *after* the call to
fix_deleted_subtree_ranges() is all that is needed, but I haven't
checked this out.

> I'm not sure why there are two different conclusions.

Again, different conclusions based on different inputs.

-- 
Paul T. Burba
CollabNet, Inc. -- www.collab.net -- Enterprise Cloud Development
Skype: ptburba