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