Jujutsu For Busy Devs, Part 2: "How Do I...?"

8 min read Original article ↗


How Do I...

Maybe it's just me, but when I first pick up a new tool and I don't yet have any muscle memory, I don't have a broad view of what commands are available, and I'm missing all the shortcuts and aliases that I can use with whatever it's replacing, I can feel a little "trapped by the tool". I'm much slower at everything I try to do, I'm not quite sure what side effects some of the commands I run will have, and I have to go look at the documentation for anything beyond the basics.

So this part is intended to be less of a linear tutorial and more like a quick reference handbook for common tasks. You might find this useful if you read part 1 and decided to try out Jujutsu, and you've picked up the basic set of commands, but you still don't feel like you're ready to start using it in earnest. Hopefully this will help to bridge the gap by broadening your view of what's available and connecting it to what you already know how to do with Git.

...See More of the Log?

jj log takes an -r argument, and you can give it an expression in Jujutsu's revset language.

See everything on the main branch:

jj log -r '::main'

See everything on the main branch and leading up to your current revision:

jj log -r '::main | ::@'

See all the revisions you authored:

jj log -r 'mine()'

See everything:

jj log -r ..

...Edit an Existing Revision?

This is the equivalent of something like a git commit --amend or what you'd do with a git rebase -i (but with jj it's also a way to navigate around, because editing a revision will move you to that place in the tree).

To directly edit an existing revision in place:

jj edit <revision ID>

Or you can create a new revision on top of the one you want to edit, make changes, and then squash those changes into it:

jj new <base revision>
# Make changes, then...
jj squash

...Insert Between Two Revisions?

You can create a new revision after (-A) or before (-B) another revision, which will insert it into the history rather than branching off on a new tangent.

jj new -A <revision ID>
jj new -B <revision ID>

To illustrate this, let's say you have these revisions:

wtpxmkup blog@maddie.wtf 19 seconds ago git_head() 14d876d1
│  Third original revision
○  qxqkllpu blog@maddie.wtf 25 seconds ago b0d0cc60
│  Second original revision
○  uvtplnry blog@maddie.wtf 29 seconds ago cd980411
│  First original revision
~

If you were to run jj new u, (without either -A or -B, so saying "create a new revision on top of u"), you'd get this result:

@  ouksmvko blog@maddie.wtf 11 seconds ago 3a5609b6(empty) (no description set)
│ ○  wtpxmkup blog@maddie.wtf 59 seconds ago 14d876d1
│ │  Third original revision
│ ○  qxqkllpu blog@maddie.wtf 1 minute ago b0d0cc60
├─╯  Second original revision
○  uvtplnry blog@maddie.wtf 1 minute ago git_head() cd980411
│  First original revision
~

The new revision, o, has been created on top of u, but is on a separate diverging branch of revisions from the others.

Instead, if you ran jj new -A u ("create a new revision after u"), you'd get this:

wtpxmkup blog@maddie.wtf 6 seconds ago 6871540d
│  Third original revision
○  qxqkllpu blog@maddie.wtf 6 seconds ago 6c461c50
│  Second original revision
@  mltnnmks blog@maddie.wtf 6 seconds ago 625a0faf(empty) (no description set)uvtplnry blog@maddie.wtf 2 minutes ago git_head() cd980411
│  First original revision
~

The new revision, m this time, has been inserted after u—so, still a child of u, but also a parent of all the revisions that were children of u before.

-B is along the same lines—you would have gotten exactly the same result if you ran jj new -B q.

...Get Rid of Changes?

If you're editing a revision and you want to get rid of the changes you made to a certain file in that revision, you can "restore" the changes:

jj restore path/to/file

You can also get rid of all changes in the revision, but keep the revision around:

jj restore

If you want to get rid of the changes and get rid of the revision, you can abandon it:

jj abandon

Restores can be of the form "restore the version of this file from a particular revision", which by default is the previous revision, but can be an older one or an unrelated one:

jj restore --from main path/to/file

This form of the command is the equivalent of git checkout main -- path/to/file.

...Split a Revision?

There's a split command:

jj split <revision ID>

This will open an interactive TUI editor to let you choose which changes you want to go into the first new revision. Everything not ticked in that editor will go into the second revision.

This command defaults to splitting the current revision, so not providing any arguments is equivalent to jj split -r @.

...Delete a Bookmark (Branch)?

First delete it locally:

jj bookmark delete the-bookmark-name

Then you can push the deletion:

jj git push --deleted

...Go Back to Git?

You basically can just start running Git commands again at any time with no destructive effects. The only thing is that running Jujutsu commands will put you into detached HEAD mode, so you'll want to check out a branch:

git checkout some-branch-name

...Undo a Mistake?

Jujutsu maintains a log of all operations that occurred (a bit like Git's reflog), and you can look at it:

jj op log

You can undo the latest operation:

jj op undo

Or you can provide that command with the ID of an operation that you found in the op log:

jj op undo <operation ID>

If you want to undo multiple operations, you can restore the repository to its state at any particular operation in the log:

jj op restore <operation ID>

...Look at a Diff?

Jujutsu's diff is similar to Git's.

jj diff -r <revision ID>

This also defaults to the current revision (@) if you don't provide an -r argument:

jj diff

You can provide it file glob patterns to look at the diff to specific files:

jj diff src/bin/**.rs

...Cherry-Pick a Change?

Jujutsu calls this operation "duplicating" a revision. You can do it in one step if you know either the revision ID or the commit ID of the revision you want to duplicate, and if you know where you want it to go.

jj duplicate <source ID> --destination <ID of destination base rev>

To make this a bit clearer, let's say there's a revision called a that you want to cherry-pick on top of a revision called b. If you run jj duplicate a --destination b, Jujustu will create a new revision called c on top of b, and it will contain a copy of the changes from a.

If you want to insert the cherry-picked revision between two other revisions, you can provide the same -A (after) or -B (before) argument to jj duplicate that you can give to jj new and other commands instead of --destination:

jj duplicate <source ID> -A <rev to insert it after>
# or...
jj duplicate <source ID> -B <rev to insert it before>

...Rebase Changes?

Jujutsu's rebase is significantly more user-friendly than Git's. Let's say we have a branch called my-cool-feature, whose first revision is a, and we want to rebase it onto main (maybe we just updated the latest version of main from the remote with jj git fetch).

First, to initiate the rebase, we can do this:

jj rebase --source a --destination main

This will move a and all of its children onto the destination, main. This is actually most like a git rebase --onto.

Jujutsu can alternatively figure out which revisions you want it to move based on the tip of the branch rather than the root:

jj rebase --branch my-cool-feature --destination main
# or (you don't have to use a branch name)
jj rebase --branch b --destination main

After you've performed the rebase, conflicts might have been created. Just jj edit the conflicted revisions, working from base to tip, and fix the conflicts manually (or you can jj new onto each conflicted revision, fix the conflicts in the new revision that's created, and then jj squash the resolution changes in).

...Merge?

Merges are just revisions with more than one parent, and jj new takes multiple arguments for the parents of the revision. To merge a branch a into a branch b, run:

jj new b a

Then you'll probably want to write a message for the new merge revision, with:

jj describe