First-Class Conflict Handling
jj treats conflicts as first-class objects—stored in commits, not blocking errors that halt your workflow.
Git's Conflict Model
In git, conflicts block everything:
git merge feature
# CONFLICT: file.js
# Can't commit until resolved
git status
# both modified: file.js
# Must resolve before continuing
vim file.js
git add file.js
git commit
jj's Conflict Model
In jj, conflicts live in commits. You can continue working, view them, and resolve when ready.
jj merge feature
# Conflict recorded in @
# Can still work
jj status
# Shows conflicts
# Can commit @ with conflicts
jj describe -m "Merge with conflicts"
jj new
# Resolve later
jj resolve
Viewing Conflicts
git
git statusjj
jj status| git | jj |
|---|---|
| git status | jj status |
Resolving Conflicts
git
vim app.jsgit add app.jsgit commitjj
vim app.jsjj resolve app.jsjj resolve --ours app.jsjj resolve --theirs app.js| git | jj |
|---|---|
| vim app.js | vim app.js |
| git add app.js | jj resolve app.js |
| git commit | jj resolve --ours app.js |
Continuing with Conflicts
In jj, you can commit with conflicts and resolve later:
# Merge creates conflict
jj merge feature
# Commit the conflict state
jj describe -m "Merged with conflicts in app.js"
jj new
# Continue working on other files
vim other.js
jj describe -m "Work on other file"
jj new
# Come back to resolve later
jj edit --change <commit-with-conflict>
jj resolve
NOTE:
This is powerful for complex merges—resolve conflicts in batches, not all at once.
Conflict Materialization
When you have conflicts, jj shows them in files:
# jj creates conflict markers
$ cat app.js
<<<<<<< Left
const x = 1;
=======
const x = 2;
>>>>>>> Right
But the conflict is also stored in commit metadata:
$ jj show @
Conflict in app.js:
Left: abc (our change)
Right: def (their change)
Base: 123 (original)
Multiple Conflicts
git
git merge featurejj
jj merge featurejj resolve file1jj resolvejj describe -m "Partial merge"jj new| git | jj |
|---|---|
| git merge feature | jj merge feature |
Abandoning Merges
git
git merge --abortjj
jj abandon @jj op undo| git | jj |
|---|---|
| git merge --abort | jj abandon @ |
Rebase Conflicts
Even with jj's automatic rebasing, conflicts can occur:
jj rebase -d main@origin
# Conflict in app.js
# Resolve and continue
jj resolve app.js
jj rebase --continue
But since jj auto-rebases descendants, you only resolve once:
git
git rebase -i HEAD~5git add .git rebase --continuejj
jj edit old-commitjj resolve| git | jj |
|---|---|
| git rebase -i HEAD~5 | jj edit old-commit |
| git add . | jj resolve |
| git rebase --continue |
Conflict Resolution Tools
git
git mergetooljj
jj resolve --tooljj resolve --ours file.jsjj resolve --theirs file.js| git | jj |
|---|---|
| git mergetool | jj resolve --tool |
Visualizing Conflicts
# See which files have conflicts
jj status
# Show conflict details
jj show @
# Show just conflicts
jj diff --conflict
Try It Yourself
# Create a conflict scenario
# Terminal 1
jj init repo1
cd repo1
echo "original" > file.txt
jj describe -m "Initial"
jj new
# Terminal 2
jj init repo2
cd repo2
echo "original" > file.txt
jj describe -m "Initial"
jj new
# Make diverging changes
# Terminal 1
echo "change1" > file.txt
jj describe -m "Change 1"
jj new
# Terminal 2
echo "change2" > file.txt
jj describe -m "Change 2"
jj new
# Now try to merge in Terminal 1
# (after somehow importing Terminal 2's changes)
# You'll get a conflict
jj resolve --edit file.txt
# Choose "change1" or "change2" or both
Key Takeaways
- Conflicts are stored in commits, not blocking errors
jj resolvemarks conflicts as resolved- Can commit with conflicts and resolve later
jj resolve --ours/--theirsfor quick resolution- Auto-rebasing means resolve conflicts once
jj op undoto abandon problematic merges
Next Steps
Finally, let's look at advanced workflows and tips for jj power users.