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

Resolving conflicts with jj resolve

In the last chapter, we resolved a conflict by opening the file and editing the markers by hand. That always works, and for a small conflict it’s often the quickest approach. We can also use jj resolve, which hands each conflicted file to a merge tool one at a time.

Since we cleaned up the conflict in our project already, I’ve made a little scratch repository to play with: two changes, “say hi” and “say hey”, that each put a different greeting in greeting.txt, and a merge of the two on top. That merge is, of course, conflicted.

First, let’s find out what’s actually conflicted:

$ jj resolve --list
greeting.txt    2-sided conflict

Two-sided is the ordinary case — two branches, two versions. You’ll see three-sided and worse if you merge more than two things at once, or conflict on top of a conflict.

Then run it:

$ jj resolve

That opens your merge editor on greeting.txt, with the two sides and the base version. Save and exit, and jj records the result; exit without changing anything and it stops, leaving the conflict alone. If several files are conflicted, you get them one after another, and you can name paths — jj resolve greeting.txt — to deal with one at a time.

Which tool opens depends on ui.merge-editor, which we’ll set in the customization section. The default is jj’s built-in editor.

Taking one side wholesale

Sometimes there’s nothing to merge because one side is simply right. There are two built-in tools that let us skip the editor:

$ jj resolve --tool :ours
Working copy  (@) now at: plwvlnst 80d01ccc merge the two
Parent commit (@-)      : tmqzyrmt ef8a1082 say hi
Parent commit (@-)      : xvqtknsp 828bdd03 say hey
$ cat greeting.txt
hi

:ours takes the first side of the conflict, :theirs the second. Be careful with the names: in a merge, “ours” is the first parent you gave jj new, and in a rebase it’s the commit being rebased onto. Check with jj diff afterwards rather than trusting the word.

Resolving somewhere else

Conflicts don’t have to be resolved in the working copy. -r points jj resolve at any conflicted commit. Back in the last chapter, when povouosx was sitting above us in conflict, we could have fixed it without moving @ at all:

$ jj resolve -r povouosx

This is worth knowing because jj’s hint suggests another approach: make a new commit on top of the conflicted one, fix it there, then use jj squash to move the fix down. Both work. The hint’s version keeps the conflicted commit untouched while we experiment, while -r edits it directly.

Afterwards

Resolving is just editing, so the usual tools apply. jj st stops warning you:

$ jj st
Working copy changes:
M greeting.txt
Working copy  (@) : plwvlnst 80d01ccc merge the two
Parent commit (@-): tmqzyrmt ef8a1082 say hi
Parent commit (@-): xvqtknsp 828bdd03 say hey

jj diff shows what you settled on, and jj undo puts the conflict back if you got it wrong. A resolution is a normal edit to a normal commit, with all the same escape hatches.

And as we saw last chapter, resolving a conflict once is enough: descendants get rebased onto the resolved version automatically, and the conflict doesn’t come back for each commit in turn the way it does with git rebase.

That’s branching, merging, and conflicts covered. Before we can get to the stacked pull request workflow those behaviors make possible, we have to talk about using jj with GitHub in the first place. Let’s go over that next.