4 ms·
> Conflicts are first-class items I have less experience working in large (heavily tracked active, e.g. 1k+ committers) repos, however we try to keep conflicts
by smaudet 2y ago
> Conflicts are first-class items
I have less experience working in large (heavily tracked active, e.g. 1k+ committers) repos, however we try to keep conflicts to a minimum - of course you can't get rid of them completely, normally merge/rebase I have close to zero conflicts on the regular, or those that I have are automatically (non-ai) merged (correctly).
I suppose I find the idea of conflicts as first class entities as somewhat intriguing, do they function as a sort of super-feature? If e.g. you have 10 features all touching the same area of code, then do you get maybe 3 combined conflict(ing) features?
At any rate, I think while it might be a useful metric, there should be an aim to keep conflicts to an absolute minimum....
- steveklabnik 2y agoOne thing that "conflicts as first class items" implies is that commits can exist in a conflicted state. This implies that you don't have to deal with conflicts right away. Let me explain. jj's rebase works just like git's rebase conceptually: take these commits and move them on top of some different commits. But unlike git, `jj rebase` will aways succeed, and succeed immediately. The resulting rebased commits/changes will just be in a conflicted state if a conflict happens. This decouples the "do the rebase" from "fix the conflicts" in time, meaning I can choose if I want to handle them right away or later. This ends up being very powerful in a few ways: for one, because it will always succeed, and succeed quickly, we can start automatically rebasing. Let's say you have a branch with three changes on it. You go back and edit the first change, because you forgot to do something. jj will then automatically rebase the two children, and you immediately see if you've introduced a conflict. You can then fix it right now, or you can continue working on change #1, until you're ready to address the conflict. Let's say you decide to do that, and the conflict originates in change #2: when you fix that conflict, #3 gets rebased again, and in this hypothetical story, the conflict goes away. It's also nice if you have a bunch of outstanding work, and you want to rebase it all on top of your latest trunk/main/master branch: run the command to do so, and it'll all get rebased right away, and you can immediately see what has a conflict and what doesn't, and fix them when you feel like it, rather than doing it right now just so that the command completes. There are secondary advantages, like the fact that this means rebases can happen in-memory, rather than through materializing files. This means it's super fast, and doesn't interrupt what you're doing. But I think those kinds of things are harder to grok than the workflow improvements.
- keybored 2y agoThat sounds like (sounds like at least) a breath of fresh air to be honest. Conflicts are very important to deal with and yet are a second-class concept in Git. And if you primarily use rebase you have to rely on git-rerere. And if you screw up a rebase? I guess you just `git rerere forget` one of those stored files? But what if you forget that it was slightly messed up? Well I guess it might just lie around as an opaque “conflict cache” resolution. At least I can use `git show --remerge diff` on a merge commit that had a conflict. That gives some after-the-fact insight.
- aseipp 2y agoIt 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.