3 ms·
It doesn't work like that. I should probably rewrite that "first class" part in the docs and use a real example (I wrote it originally), but basically it comes
by aseipp 2y ago
It doesn't work like that. I should probably rewrite that "first class" part in the docs and use a real example (I wrote it originally), but basically it comes down to this:
When a commit is in a conflicted state, the fact it is conflicted is recorded inside the commit object.
Let's say I have a base B which is the main branch, three commits X Y Z that do 3 different things, and my set of changes that aren't committed, called "@".
B (main) ---> X ---> Y ---> Z ---> @
Now someone pushes to main, so the full graph now actually looks like this with the new main called B'
B ---> B' (main)
\
\---> X ---> Y ---> Z ---> @
Let's say that B' has a change that will cause a conflict in Y. What does that mean? It means that if we were to change the parent of X to B', then the state of Y would be invalid and thus in conflict, because the changes are not compatible with the state of the system.
`jj rebase -d main -s X` will give you a graph just like this, with such a conflict:
B ---> B' ---> X ---> Y ---> Z ---> @
C C C
The marker 'C' means "This commit is conflicted." Note that all descendants of a conflicted commit are conflicted too, unless they solve the conflict. (This sentence is phrased very carefully because I'm about to show you a magic trick.)
Okay... So now in my filesystem, I can go see the conflict and I resolve it. Maybe Y renamed a variable that B' got rid in file foo.c, or something.
So now I can solve this conflict. But how do I resolve it? This is too much of a topic to discuss in general but here is the magic trick:
- Solve the conflict in your working copy
- Move the conflict resolution into the first conflicted commit, Y
- The conflict resolution will be propagated to all descendants, just the same way the conflict itself was propagated.
Step 1: solve the conflict in your working copy. Now my history looks like this.
B ---> B' ---> X ---> Y ---> Z ---> @
C C
Note: @ is no longer conflicted! We solved the conflict there, so it is OK. Now how do we resolve the conflict in Y and Z?
Step 2: `jj squash --from @ --into Y --interactive` will move any changes you select in a diff editor and then move that diff into the other commit.
Now the graph looks like this:
B ---> B' ---> X ---> Y ---> Z ---> @
I moved the resolution of the conflict into Y. And so the resolution of the conflict is propagated to Z.
Step 3: There is no step 3. You are done.
So the secret is that, Jujutsu tracks conflicts and the relationships between conflicts in the commit graph, just like commits. This is why they are "first class." Git basically doesn't do any of this. A commit with a conflict and a commit without one are indistinguishable in Git, unless you look at the actual diff and see rejected hunk markers. A conflicted hunk in a modified file in Git is no different than any other hunk.
This is already too long but as an addendum what I used to do is describe the conflict support as "git rebase --update-refs, combined with git rerere, on 1000x steroids." Except that's actually a shit way to describe this functionality, because only like 5 people on Planet Earth know about --update-refs or rerere. So you really need to experience it yourself or see a step-by-step, I'm afraid.
- smaudet 2y agoThat all makes a lot of sense! I didn't know about git rerere - maybe I'll make a point to try jj out if it has optimized the diff resolution... It also closely matches how I actually work, but the twist that you get to pick what time to perform the resolution. If you frequently integrate, your changes are considered newer than whatever was downstream, and so you don't have to re-perform any of your changes....buuut if you have a stack of commits that can take a long while. Plus, you might upset your IDE if there are frequent file changes, so I avoid integrating too often... It's a nice concept. My one question - can you track history across branches? I frequently "save old state" just in case I need something pre-integrate or my IDE is dumb and something's locked/gets wiped/AV freaks out and deletes my repo files... It sounds like you could sort of easily build that sort of thing on top...