-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Are moves calculated correctly when there are ambiguous matches? #90
Comments
The job here is to add a failing test that exposes any such problem in
|
While we're at it, maybe it's time to join up the tracking of moves with change migration? I'm not sure if this is feasible, though, and there is some code here that is probably saving the day for the change migration situation - perhaps that is good enough? |
Idea: cutover the association of changes with potential destination keys to being an association with potential source keys. Generalise the association, so that even straight moves without any changes have their sources added as a key without a change (neither deletion nor edit, so this becomes an optional associated value). This allows potential move destinations to be tied back to their sources by looking at the relevant ambiguous matches and seeing if a corresponding key exists in the association. |
Issue #91 raised this:
Debugging this assertion reveals a malformed MoveDestinations(Set(),Set(),Set(Section(
label = "THEIRS: 7b4fd68698f8ed6b13b067cf87f6abb25db52da1",
path = /Users/gerardmurphy/IdeaProjects/kineticMerge-dogfood-examples/src/test/scala/com/sageserpent/kineticmerge/core/CodeMotionAnalysisExtensionTest.scala,
start = TextPosition(line = 629, characterOffset = 48),
onePastEnd = TextPosition(line = 636, characterOffset = 14),
startTokenIndex = 2541,
sizeInTokens = 18,
content summary = """).get
),
... (
contentsByPath = Map(
"""
)),Set()) This is referenced in LeftAndRight(Section(
label = "OURS: 5dbb4faca2a2df4351c9f4892da59601913489f3",
path = /Users/gerardmurphy/IdeaProjects/kineticMerge-dogfood-examples/src/test/scala/com/sageserpent/kineticmerge/core/CodeMotionAnalysisExtensionTest.scala,
start = TextPosition(line = 736, characterOffset = 61),
onePastEnd = TextPosition(line = 742, characterOffset = 12),
startTokenIndex = 3543,
sizeInTokens = 18,
content summary = """).get),
... (
contentsByPath = Map(
"""
),Section(
label = "THEIRS: 7b4fd68698f8ed6b13b067cf87f6abb25db52da1",
path = /Users/gerardmurphy/IdeaProjects/kineticMerge-dogfood-examples/src/test/scala/com/sageserpent/kineticmerge/core/CodeMotionAnalysisExtensionTest.scala,
start = TextPosition(line = 629, characterOffset = 48),
onePastEnd = TextPosition(line = 636, characterOffset = 14),
startTokenIndex = 2541,
sizeInTokens = 18,
content summary = """).get
),
... (
contentsByPath = Map(
"""
)) So what happened to the left hand side of the left-right match? To start with, consider the right - that was picked up as a right edit, being part of a coalesced run: 17:23:13.584 [io-compute-3] DEBUG c.s.kineticmerge.core.merge$ -- Right edit of Section(
label = "BASE: 1e55332f",
path = /Users/gerardmurphy/IdeaProjects/kineticMerge-dogfood-examples/src/test/scala/com/sageserpent/kineticmerge/core/CodeMotionAnalysisExtensionTest.scala,
start = TextPosition(line = 666, characterOffset = 55),
onePastEnd = TextPosition(line = 669, characterOffset = 33),
startTokenIndex = 3266,
sizeInTokens = 10,
content = """expected)))
mergeResult match
case FullyMerged(result"""
) into Section(
label = "THEIRS: 7b4fd68698f8ed6b13b067cf87f6abb25db52da1",
path = /Users/gerardmurphy/IdeaProjects/kineticMerge-dogfood-examples/src/test/scala/com/sageserpent/kineticmerge/core/CodeMotionAnalysisExtensionTest.scala,
start = TextPosition(line = 629, characterOffset = 48),
onePastEnd = TextPosition(line = 636, characterOffset = 14),
startTokenIndex = 2541,
sizeInTokens = 18,
content summary = """).get
),
... (
contentsByPath = Map(
"""
), coalescing with following insertion of Section(
label = "THEIRS: 7b4fd68698f8ed6b13b067cf87f6abb25db52da1",
path = /Users/gerardmurphy/IdeaProjects/kineticMerge-dogfood-examples/src/test/scala/com/sageserpent/kineticmerge/core/CodeMotionAnalysisExtensionTest.scala,
start = TextPosition(line = 636, characterOffset = 14),
onePastEnd = TextPosition(line = 636, characterOffset = 47),
startTokenIndex = 2559,
sizeInTokens = 6,
content = "renamedPath -> tokens(leftContent"
) on the right. The left hand side of the match however was picked up as an unrelated coincident insertion: 17:23:13.594 [io-compute-3] DEBUG c.s.kineticmerge.core.merge$ -- Coincident insertion of Section(
label = "OURS: 5dbb4faca2a2df4351c9f4892da59601913489f3",
path = /Users/gerardmurphy/IdeaProjects/kineticMerge-dogfood-examples/src/test/scala/com/sageserpent/kineticmerge/core/CodeMotionAnalysisExtensionTest.scala,
start = TextPosition(line = 736, characterOffset = 61),
onePastEnd = TextPosition(line = 742, characterOffset = 12),
startTokenIndex = 3543,
sizeInTokens = 18,
content summary = """).get),
... (
contentsByPath = Map(
"""
) on the left and Section(
label = "THEIRS: 7b4fd68698f8ed6b13b067cf87f6abb25db52da1",
path = /Users/gerardmurphy/IdeaProjects/kineticMerge-dogfood-examples/src/test/scala/com/sageserpent/kineticmerge/core/CodeMotionAnalysisExtensionTest.scala,
start = TextPosition(line = 832, characterOffset = 39),
onePastEnd = TextPosition(line = 839, characterOffset = 8),
startTokenIndex = 3279,
sizeInTokens = 18,
content summary = """).get
),
... (
contentsByPath = Map(
"""
) on the right. |
Regarding that last comment, it seems that not only can preservations be accidentally picked up by the move destination machinery, so can coincident insertions. Now it can be correct for a coincident insertion to form a move destination - a coincident move, to be specific, but here we have some unrelated right edit that is ambiguous as a match with the one constituting a plain coincident insertion that in itself is a degenerate move. It doesn't seem right to consider the right edit as being a move destination when the other end of the match has been claimed by the coincident insertion. A further oddity is why degenerate moves are even modelled - if there is no source for the move, we would not consider a left- or a right-insertion is being a degenerate move, so why consider coincident insertions (or edits) as defining a degenerate move if no source can be found? We can't propagate changes this way, nor can we anchor. What was the point of this? |
As of 3d3ede6, The pesky invariant and then later post-condition mentioned in #91 has been removed altogether - there is no longer anything to police, as the invariant in The problematic merge from #91 now goes through - albeit very slowly, its performance is not in the purview of this ticket. So back to main thrust of this ticket... |
Now have a failing test with a preservation being mixed up with a genuine move in 5735cc4... |
TODO:
|
As of 73c3260, all the tests pass bar There is also some weirdness in how the move destinations report is populated, where there are multiple move destinations that share the same sources. I suspect this has been the case for some time; this is due to the code in Given how |
Yes, looking on the main branch at c008716 reveals the same behaviour - move destinations sharing the same sources are being partitioned. Let's fix that on the side and merge in... |
Trying to fix the partitioning of move destinations sharing the same sources leads to 8b1217f. This introduced a breaking invariant and then fixes that, but has broken other tests. Merging that work with the branch for this ticket also changes the outstanding test failure in Need to stop and think about this all... |
Currently looking at 0646056, which is the merge of both branches, namely changes (and straight moves) being associated with sources, and the move destinations report being subject to an invariant that distinct move destinations instances cannot share sources. What's gone wrong on the second branch is this code that works out the matches that form a key to a move destination. The idea was to get back to the sources and then fan back out to grab all the matches, thus avoiding partitioning. The problem is that the fan-out pulls in all matches, and these aren't necessarily genuine code motion. This is a similar problem to the original one that motivated this ticket, only this time it's occurring in the code that notes the move destinations, as opposed to noting the sources of the moves and any content that might need to be migrated! |
What I've learned so far...
|
As of d1973bb, got a work-in-progress implementation that passes all tests in However, that last test does show the correct behaviour of keeping preservations and moves separate as regards edit / delete migrations, so this is progress. What is bad is:
What is good is:
|
Regarding that last point about coincident entries being represented in unresolved form, perhaps we could represent then as insertion versus insertion conflicts or edit versus edit conflicts in the implementation of If so, that would allow three-way resolution to be done in I'm not sure whether to add a pair of substitutions for the left- and right-side contribution to the three-way resolution there, and then to add additional pairs of substitutions referring to the two-way resolution for all the speculative coincident merge destinations that didn't turn out to be moves after all? This may need breaking out into its own ticket... |
Finally got the dreaded The implementation is a bit hacked, and There is still the outstanding resolution work to finesse, but that can go into its own ticket. The question is, given how quintessentially nasty the insertion migration code is, is it worth making a final push on this ticket to get the tests passing, or should this segue into #83? |
The story so far as of 99e7322. Got It seems that the idea of evaluating speculative content migrations by source against speculative move destinations is sound in itself, but this doesn't play well at all with the insertion migration. This may be down to the way that divergent moves are dealt with (because we have an intra- versus inter-file move across the left- and right-side), or perhaps the awkward treatment of the speculative conflicts where a speculative edit migration is picked up isn't playing well with insertion migration. Another gotcha lurking in the wings as how coincident insertions / edits are dealt with - resolution is applied on the fly in a desparate attempt to conform to Similarly, conflicts are a bit hokey - each side gets its own linear sequence of sections, but we know from the conflict handling in the merge algebra exactly where the conflicting runs are. This isn't quite the same as handling unresolved elements, but again feels messy. Finally, I wonder whether the merge algebra from |
As of ec9fef6, there is just one test case failing in // BASE - ORIGINAL FILE.
protected val heyDiddleDiddleInModernForm: String =
"""
|Hey diddle diddle,
|The cat and the fiddle,
|The cow jumped over the moon;
|The little dog laughed
|To see such sport,
|And the dish ran away with the spoon.
|""".stripMargin
// LEFT - RENAMED FILE.
protected val heyDiddleDiddleInPsychoticForm: String =
"""
|Hey diddle diddle,
|The cat and the fiddle,
|The cow, laughing madly, ran away with the spoon and then jump'd over the moon;
|The little dog laugh'd
|To see such craft,
|And the fork ran away with the spoon.
|""".stripMargin
// RIGHT - ORIGINAL FILE.
protected val heyDiddleDiddleWithIntraFileMove: String =
"""
|Hey diddle diddle,
|The cat and the fiddle,
|The cow jumped!
|The little dog laughed
|To see such sport,
|And the dish ran away with the spoon over the moon.
|""".stripMargin
// EXPECTED - RENAMED FILE.
protected val heyDiddleDiddleInPsychoticFormExpectedMerge: String =
"""
|Hey diddle diddle,
|The cat and the fiddle,
|The cow, laughing madly, ran away with the spoon and then jump'd!
|The little dog laugh'd
|To see such craft,
|And the fork ran away with the spoon over the moon.
|""".stripMargin What the merge comes up with is:
|
The thing is, bar the duplication of the So why shouldn't the inserted text be migrated to the two anchor move destinations? Yes, it is part of a divergent insertion on the left and right, but so what? We no longer model such things as being interesting as they have no source nor associated migration content, so they are not considered special. The treatment of the It's time to admit this has gone far enough. |
The oustanding failing test case has been disabled, closing this to continue the work in the context of #83. |
This went out on Release 1.4.0. |
This came up when thinking about how to implement #83 .
Change migration relies on
MatchesContext
to recognise the source of moves when there are deletions and edits to be migrated.MatchesContext
also recognises the destination of moves in the context of handling insertions and edits. Amongst other things, this forms of part of finding anchors as a prelude to performing insertion migration. The move destinations report is also built up from this.So far, so good - only both areas of code examine matches to complete the picture of either where the move is going (for change migration) or where the move has come from (for move destinations).
This isn't always correct when there are ambiguous matches; yes, it is expected to have a valid situation where a change might be migrated to several destinations (all on the same side of the overall merge), or to have several anchors with the same content (all on the same side of the overall merge) being moved to the same location. A mix of these is possible with multiple sources and destinations too.
Where it falls down is when there is an obvious move of content from a source location to a destination location - either intra-file or inter-file, but elsewhere there is a simple preservation of content that matches the moved content. We do not want to consider the parts of the preservation is being candidates for a move source or destination.
The text was updated successfully, but these errors were encountered: