Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

How Are Conflicts Handled?

Important

To satisfy your curiosity, you look under Jujutsu’s hood, and grasp what “conflicts are first-class citizens” means.

Two Philosophies

Git and Jujutsu fundamentally differ in what they think a conflict is.

To begin with, in Git there is no such thing as a “conflicted commit”. A conflict is not a Git object, but a state blocking operations until completely resolved.

In Jujutsu, conflicts are part of history, and can be manipulated like Changes. Operations creating conflicts don’t fail; conflicts are recorded for you to resolve later. Best of all, “editing broken files” isn’t the only way out.

But “being first-class citizen” is a very vague concept. Let’s try to get a better grasp of it.

What’s Inside A Commit

In both Git and Jujutsu a non-conflicted commit holds:

  • Pointers to its parents (that is, its position in history).
  • A pointer to exactly 1 Tree: a coherent photograph of the project’s filesystem.
A Git Tree pointing to 2 Blobs and 1 Tree

From here, Git and Jujutsu dramatically diverge.

Conflicts in Git

All Git operations are atomic: they either succeed or roll back leaving the repository exactly as it was. Except during conflicts. When merging fails, Git cannot forge a coherent Tree, so it is stuck halfway through of an incomplete transaction. So it:

  • Gives up creating the next dommit.
  • Blocks the repository.
  • Hands the problem over to you.

Git does it best to help you solve the conflict, spreading helpers in 3 places:

  • In the index: for each conflicted file, 3 versions (:1 ancestor, :2 ours, :3 theirs).

  • In the working copy: this is the only time when Git violates its own rules and tampers with your files, filling them with conflict markers <<<<<<<, =======, and >>>>>>>.

  • In .git: a bunch of state files like MERGE_HEAD to keep track of the transaction.

In the meanwhile, your repo remains an exceptional no-fly zone.

Conflicts in Jujutsu

Jujutsu doesn’t panic. It doesn’t even inject conflict markers on disk. It creates the Change anyway. But how?

  • Position? Easy, the parents are known.

  • Content? All Jujutsu knows is that a consistent Tree must be somehow derived combining the Parent 1’s Tree, the Parent 2’s Tree and the Base’s Tree.

Why not point to all of them? After all: nothing prevents a Change from targeting more than one Tree, like in Git.

Conflicted Changes

Yep. A Change internall hold always a collection of Trees. That’s the trick.

  • If that collection contains 1 Tree only, then it’s an unconflicted Change.
  • If there’s more than 1, then there is merge to complete.

This normalization lets Jujutsu treat all Changes consistently, no matter the conflicts.

The Algebra Of Conflicts

A convenient way to represent the Trees referenced by a Change is as an algebraic expression with + (apply) and - (diff) signs:

  • T − F is the diff between the content of Change T and the content of Change F, so the changes to apply from F to get to T.
○ T  ─╮
│     │   T - F
○ F  ←╯
  • F + diff means applying the edits diff on top of a the content of Change F, getting back a new Change T.
                              ○ T        ─╮
           + diff      ═══⇒   │           │   T - F = diff
○ F                           ○ F        ←╯

A failed merge between X and Y is stored as Y + (X − B). It means:

Apply the diff (X - B) onto Y.

In the happy scenario, Jujutsu will execute that operation, converging to a conflictless single Tree.

It it can’t reduce this to 1 Tree, it records [X, Y, B] meaning: “I need to do Y + (X − B), help me.”

A Concrete Example

Say greetings.txt exists in B, X and Y with these content:

○    Y    hello mundo
│ ○  X    hola world
├─╯
○    B    hello world

Merging X and Y you get:

×    M                <- conflict (we would like "hola mundo")
├─╮
│ ○  Y    hello mundo
○ │  X    hola world
├─╯
○    B    hello world

Both changes touch the same line, so you get a conflict. M records X + (Y − B). Jujutsu failed to reduce it, but M exists anyway as an ordinary Change. You can jj edit M to resolve the conflict whenever you like.

Simplifications

If you apply the diff (X - B) to its natural base B you get:

B + (X - B)

The grade school student in you recalls that B and -B cancel, so this simplifies algebraically to:

X

Wrongly Applied Diffs

This lets you interpret a conflict as those cases where you cannot simplify algegraic expressions, or:

A conflict is a diff applied to the wrong base.

The diff (X - B) applies cleanly to base B:

B + (X - B) = X

Apply it to the wrong base:

Y + (X - B)

and you might get a conflict. Jujutsu stores [Y, X, B] hoping you either edit files, or move Changes so algebra simplifies the conflict away.1

Markers Are An Illusion

When you visit a Conflicted Change, you’ll find conflict markers. Yet conflict markers don’t exist in Jujutsu’s repositories: they are generated on-the-fly when you visit a Conflicted Change.
When you save, Jujutsu parses them back into logical states.

Because they are dynamic, you can choose between 3 built-in styles, including the original Git’s one.


  1. How can possibly algebra simplify the conflict away? If you at curious, check out Back to Grade School!