Resolving merge conflicts
Reset your progress
Reset your progress
To reset your progress to the start of this chapter, run the following command:
curl https://s2p2.github.io/jj-for-everyone/reset.sh | bash -s conflict
cd ~/jj-tutorial/repo
After learning about loops, Alice wants to update her Python script.
jj new 'description("WIP: Add for loop")'
She fixes the incorrect loop syntax in hello.py.
for _ in range(10):
print("Hello, world!")
You can edit the file manually or run this command:
echo 'for _ in range(10):
print("Hello, world!")' > hello.py
Then she commits the correction:
jj commit -m "Fix loop syntax"
@ zzlzryut alice@local 2025-08-23 21:21:54 5b7e967e │ (empty) (no description set) ○ oxnuoryz alice@local 2025-08-23 21:21:54 git_head() c6042124 │ Fix loop syntax ○ tvrzpsvy alice@local 2025-08-23 21:21:53 push-tvrzpsvypkqk 6e468237 │ WIP: Add for loop (need to fix syntax) │ ◆ quvtvrzl alice@local 2025-08-23 21:21:54 main d0c3efcd │ │ Print German and French greetings as well │ ~ (elided revisions) ├─╯ ◆ krymmwqm alice@local 2025-08-23 21:21:53 1581674f │ (empty) Merge code and documentation for hello-world ~
Creating a conflict
Alice now merges her changes into main with a new commit:
jj new main @-
Working copy (@) now at: srpklysn 0f64cf5a (conflict) (empty) (no description set) Parent commit (@-) : quvtvrzl d0c3efcd main | Print German and French greetings as well Parent commit (@-) : oxnuoryz c6042124 Fix loop syntax Added 1 files, modified 2 files, removed 0 files Warning: There are unresolved conflicts at these paths: hello.py 2-sided conflict
This results in a merge conflict. The new commit is marked with (conflict) because both main and Alice's branch modified hello.py. Jujutsu cannot automatically combine these changes.
@ lozqokkt alice@local 2025-08-23 21:34:35 fe21ff1e conflict ├─╮ (empty) (no description set) │ ○ oxnuoryz alice@local 2025-08-23 21:21:54 c6042124 │ │ Fix loop syntax │ ○ tvrzpsvy alice@local 2025-08-23 21:21:53 push-tvrzpsvypkqk 6e468237 │ │ WIP: Add for loop (need to fix syntax) ◆ │ quvtvrzl alice@local 2025-08-23 21:21:54 main git_head() d0c3efcd │ │ Print German and French greetings as well ~ │ (elided revisions) ├─╯ ◆ krymmwqm alice@local 2025-08-23 21:21:53 1581674f │ (empty) Merge code and documentation for hello-world ~
Reading conflict markers
The conflicted file hello.py now contains markers showing the changes from both sides of the merge.
<<<<<<< Conflict 1 of 1
%%%%%%% Changes from base to side #1
print("Hello, world!")
+print("Hallo, Welt!")
+print("Bonjour, le monde!")
+++++++ Contents of side #2
for _ in range(10):
print("Hello, world!")
>>>>>>> Conflict 1 of 1 ends
The content between <<<<<<< and >>>>>>> shows the conflicting changes. The "side" numbers correspond to the order of the parent commits in the jj new main @- command.
side #1ismain(the first parent). Its changes are shown in a diff format:+(plus sign): An added line.-(minus sign): A removed line. A modified line is shown as a-followed by a+.- A space (
): An unchanged line, shown for context.
side #2is@-(the second parent). Its section shows the final content from Alice's branch.
Fixing a conflict
Resolving a conflict requires manually editing the file to the desired state, as an automatic merge is impossible. The correct outcome depends on intent.
By example, there are three different reasonable merges of these changes
By example, there are three different reasonable merges of these changes
-
Only repeat the English greeting:
for _ in range(10): print("Hello, world!") print("Hallo, Welt!") print("Bonjour, le monde!") -
Repeat all languages and interleave them:
for _ in range(10): print("Hello, world!") print("Hallo, Welt!") print("Bonjour, le monde!") -
Repeat all languages but keep them separate:
for _ in range(10): print("Hello, world!") for _ in range(10): print("Hallo, Welt!") for _ in range(10): print("Bonjour, le monde!")
Here, we'll combine the changes to repeat all greetings.
Replace the content of hello.py with the correct code, removing all conflict markers.
echo 'for _ in range(10):
print("Hello, world!")
print("Hallo, Welt!")
print("Bonjour, le monde!")' > hello.py
With the file saved, the conflict is resolved. The jj log will no longer show the (conflict) marker.
@ kprnqvtk alice@local 2025-08-24 07:41:36 b46df36e ├─╮ (no description set) │ ○ ttuuwuvl alice@local 2025-08-24 07:38:02 c8479848 │ │ Fix loop syntax │ ○ uxpxzolo alice@local 2025-08-24 07:38:01 push-uxpxzoloznts b326e556 │ │ WIP: Add for loop (need to fix syntax) ◆ │ wruksqvz alice@local 2025-08-24 07:38:02 main git_head() 118457e4 │ │ Print German and French greetings as well ~ │ (elided revisions) ├─╯ ◆ kropmvym alice@local 2025-08-24 07:38:01 8c90e9fc │ (empty) Merge code and documentation for hello-world ~
A conflict resolution is considered a change itself, so the commit isn't marked as "(empty)" like the first merge commit we made. Let's commit this and push to main:
jj commit -m "Merge repetition and translation of greeting"
jj bookmark move main --to @-
jj git push
Conflicts during a rebase
Conflicts during a rebase
Conflicts can also occur during a rebase, but these are more complex to resolve. If you encounter one, for now, it's best to abort with jj undo and create a merge commit instead.
Tools for conflict resolution
Tools for conflict resolution
While manually editing conflicted files always works, various tools can simplify the process.
Jujutsu has built-in tools like :ours and :theirs to quickly pick one side of a conflict and discard the other.
# keep "our" changes, throw away "theirs"
jj resolve --tool :ours
You can also integrate powerful external tools. For example, Mergiraf resolves conflicts using the programming language's syntax tree. After installing it, run:
jj resolve --tool mergiraf
Jujutsu also supports custom merge tool configurations.