-
Notifications
You must be signed in to change notification settings - Fork 346
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
Fix propagation of conflict resolution with tree-level conflicts #2287
Conversation
All non-test callers already have a `Merge` object, so let's pass that instead. We thereby simplify the callers a little, and we enforce the "adds.len() == removes.len() + 1" constraint in the type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sgtm, but can you take another look, @ilyagr ?
This is a confusing bug. It might take me a bit of time to write everything down and think through what simplifications happen when. (I'm assuming this is not urgent. If it is annoying enough, we could merge this and then think about moving/removing some simplifications later. We could also add a |
bbfe3a8
to
82b54d2
Compare
Not urgent, but I would like to have it fixed before we recommend enabling tree-level conflicts to people. I think I've run into this bug twice myself, but I may misremember. If no one else who has enabled tree-level conflicts has run into it, then it's probably low priority. |
I think I figured out a simpler way to understand this bug. Ignoring the difference between I have a demo in https://github.com/ilyagr/jj/commits/my-pr/2287, see especially the commit with the artful name "resolve 1". I think we should make it a rule that when we have two operations that simplify to different degrees, both should be idempotent, and the combination of the two in either order should be equivalent to the stronger of the two. (In principle, In the test you wrote, your fix fixes the problem I described as well as the bug you saw. However, I think that perhaps we should solve this in a way that more clearly corresponds to the problem as I described it. One alternative that seems to then exist is to put a loop inside Your solution might easily be more efficient, but it feels like black magic and I can't think of a clear invariant it creates or decide whether it makes WDYT? |
I think that's correct, except that By the way, we don't actually use
Right, that's one of the solutions I mentioned, except that I didn't suggest doing it in a loop. It was my first thought as well. However, I think the only thing that can be different on the next attempt to resolve conflicts is hunk-level merging. I don't think
Hopefully my explanation above make it feel a bit less like black magic. |
Thank you for the explanation!
It's a very good point that
Idle thoughts: Ultimately, we could write
Ah, I didn't realize that, but it would make sense.
This is not obvious; I will need to think about this more. Assuming you are correct, I'd still prefer some more explicit explanations of this invariant and verification that it holds. We could, for example, put it some BTW, the statement that At this point, my suggestions are a bit vague, so I leave it up to you to what degree (if at all) you want to address them in this PR. At this point, I think we're mostly on the same page at least about what the intention is of how the bugfix should work, and we can change things up beyond this PR.
Not right at this point, but maybe in a bit of time :). |
I ran into a bug the other day where `jj status` said there was a conflict in a file but there were no conflict markers in the working copy. The commit was created when I squashed a conflict resolution into the commit's parent. The rebased child commit then ended up in this state. I.e., it looked something like this before squashing: ``` C (no conflict) | | B conflict |/ A conflict ``` The conflict in B was different from the conflict in A. When I squashed in C, jj would try to resolve the conflicts by first creating a 7-way conflict (3 from A, 3 from B, 1 from C). Because of the exact content-level changes, the 7-way conflict couldn't be automatically resolved by `files::merge()` (the way it currently works anyway). However, after simplifying the conflict, it could be resolved. Because `MergedTree::merge()` does another round of conflict simplification of the result at the end of the function, it was the simplifed version that actually got stored in the commit. So when inspecting the conflict later (e.g. in the working copy, as I did), it could be automatically resolved. I think there are at least two ways to solve this. One is to call `merge_trees()` again after calling `tree.simplify()` in `MergedTree::merge()`. However, I think it would only matter in the case of content-level conflicts. Therefore, it seems better to make the content-level resolution solve this case to start with. I've done that by simplifying the conflict before passing it into `files::merge()`. We could even do the fix in `files::merge()`, but doing it before calling it has the advantage that we can avoid reading some unchanged content from the backend.
82b54d2
to
51b5d16
Compare
Yeah, that makes sense. I've added a |
Checklist
If applicable:
CHANGELOG.md