My most frequently used Jujutsu VCS commands

A cheat-sheet of jj commands you're most likely to need day-to-day using jj as a git replacement.

by on

A bit over three months ago, I switched to using the Jujutsu Version Control System as my daily-driver frontend to git. Since then I’ve been able to refine my workflow to the point where I am much more productive than I was with git. A key component of those productivity gains was becoming familiar with just a few commands. I’m sharing these commands with you as a cheat-sheet if you are considering replacing git with jj.

On the 7th of April, 2025, I joined Imbue as a Member of Technical Staff. At Imbue, git is the standard VCS used by the engineering team. I had been curious about the Jujutsu Version Control System (jj), and decided to attempt to make the switch while joining.

Jujutsu makes it easy to interoperate and collaborate with others using a git repository, but memorizing the idiomatic replacements to my existing git workflow with the equivalent jj commands took a bit of time. Answers to my questions were availible in many excellent online resources like Steve Klabnik’s Jujutsu Tutorial and the jj reference. However, this would come at the expense of pulling me out of the flow of the task I was working on.

Over the past 3 months, I have been refining the jj commands that I keep in my short-term memory, to build a smooth process that is a superior replacement for the git flow I previously enjoyed. I find myself having to consult the references extremely rarely–not because I have committed more of jj to memory, but because I have learned the subset of jj that is likely to come up during the working day.

I believe that Zipfian distributions rule over many of our human experiences. In Zipfian distributions, the frequency of any outcome is inversely proportional to its rank, meaning that the second most-common outcome occurs roughly half as often as the most common, and so on. In practice, this means the distribution is dominated by the few most-common outcomes.

You might also think of the Pareto Principle or 80-20 rule which states that 80% of the outputs are caused by 20% of the inputs. What follows are my “20% inputs” that I found most useful.

I generated this list by running the following shell snippet:

history |  # All my history
grep "jj" | # Find only the commands with jj in them
grep -v grep | # Exclude grep
# Remove the line number that history prints
sed 's/^ *[0-9]\{1,\} *//' |
# Duplicate commands without their final argument
sed -E 'h; s/[^[:space:]]+[[:space:]]*$//; G' |
sort | uniq -c | sort -n | # Standard UNIX-ism to order by count
tail -n 40 # Give me the highest 40 frequencies

I chose to duplicate commands without their final argument so that commands that only varied by the final argument would be grouped together and counted. I then edited the results by hand, taking out commands that I no longer use or commands that had been incorrectly truncated.

The final result looked like this. I’ve removed the exact counts, but you can see what amounts to a power law.

Fig. 1: Histogram of my most frequent jj commands

A look at each of the commands

jj edit <-r/-b> <ident>

I was surprised to find that jj edit is my most commonly used command. There are two main use cases:

The first is as a replacement for git switch/git checkout.

The second is to jump back in time to fixup a prior revision if I’ve forgotten something. With git, I would infrequently use git rebase -i to edit a prior commit. Since that operation was quite tedious, I would only edit previous commits in severe cases. Because jj reduces the friction to editing prior changes, I find myself doing it much more often.

jj b c -r@ <branchname>

This is shorthand for jj bookmark create --revision @, and @ itself is shorthand for the current revision.

This is the equivalent to git’s git checkout -b <branchname> or git switch -c <branchname>, both of which begin a new branch.

Bookmarks in jj behave similarly to branches when uploaded and viewed in Git hosting platforms. There are a few small differences when viewing and editing them locally, which you can feel your way through.

jj rebase -b@ -dmain

The jj equivalent to git rebase

In my workflow, I often want to rebase my current branch onto main. I used to spell this in various different ways, some of which dynamically depended on the name of the bookmark I was rebasing.

I’ve recently settled on jj rebase -b@ -dmain which does not vary each time I call it. This is constantly accessible through C-r in my terminal, and it always does the right thing.

jj show and jj diff

I seem to use jj show and jj diff interchangeably to view the changes that are pending with the current revision. When called with no arguments, they assume the default argument of -r@ which signifies the current revision.

Less frequently, I also use these commands to look at other revisions in the tree when I just want to view the changes without visiting that revision.

I also use jj diff to specifically look at the diff of single files, or to see changes between revisions, but these occurrences are very rare.

jj new -r main

I use this command to start a new branch off main. Usually I will follow this up with jj b c -r@ <branchname>. This corresponds to the named bookmark workflow described at that link.

jj git fetch

Equivalent to git fetch, this updates my local state to be the same as the remote.

A couple of caveats to keep in mind when using this: * This automatically updates all named, non-owned bookmarks, even the ones I’m not currently visiting. In particular, this will automatically update main. * This will automatically delete any bookmarks that have been deleted from the remote repository. Usually they have been deleted by me, because I merged and deleted them in Gitlab, but I can imagine this could drop a bookmark that I wanted.

jj restore

jj restore is an analog to git restore. It allows you to checkout a file from a given revision. For old-timers who were used to the old git checkout -- syntax, this is much more intuitive to learn and use.

You could theoretically, use jj restore filename --from some-revision --to other-revision, to restore a file between two revisions without checking them out yourself. When I’m restoring a file I’ll usually want to examine how that file is going to interact with the other files around it, and to run tests. I’ll first move to the destination revision, and then restore to the current revision (@) which is the default behaviour when --to is not passed.

jj git push

Just like git push, this will push updates to your tracked bookmarks to the remote repository.

I almost always use the flag --allow-new, which allows new bookmarks I’ve created to be pushed to the remote. In my workflow, there is never an occasion where I would want to have a branch locally that isn’t pushed up to my remote. If you need to, jj can be configured to refuse to push certain commits to git

jj b f <branchname>

Short for jj branch forget <branchname>, this command will remove the branch from your local set of bookmarks, while still keeping it in the remote. This is useful when I need to check out a colleauge’s branch to inspect it, but don’t want to stay subscribed to edits until that branch closes.

jj desc -m <description>

This command sets or changes the description for the current revision. Unlike git, you are allowed to have revisions without descriptions, but jj will refuse to push them to remotes.

Although jj new -m <description> exists, I never use that, and instead choose to use a second command to set the description.

jj st

Just like git status, this will get the status of the current revision in jj.

Unlike git, in jj you are always working on a revision, and so there’s never the concept–or worry–of a dirty working copy.

jj tug-

Bookmarks in jj do not automatically update. Since my preference is to commit often and to push early, I’m usually adding commits on top of bookmarks, and then pushing them.

jj tug is an alias that I found online attributed here. It updates the closest bookmark ancestor to point to the current revision, giving me an easy command to type before I jj git push

To set it up, I just add the following two commands to my .jjconfig.toml:

[aliases]
tug = ["bookmark", "move", "--from", "heads(::@- & bookmarks())", "--to", "@"]
tug- = ["bookmark", "move", "--from", "heads(::@- & bookmarks())", "--to", "@-"]

tug- updates the bookmark to the previous revision, while tug updates it to the current one. It’s useful to have both versions, since you might be working on the current revision, and so don’t want to push it just yet.

jj log

Similarly to git log, this shows the current history of all revisions.

jj abandon

I almost always use jj abandon on leaf revisions, to discard a draft of an idea or a false start. One gotcha to be careful of is that if you jj abandon a commit with a bookmark, jj will also delete that bookmark. If you then do a jj git push, this will push that deletion to the remote, potentially closing your open pull requests.

You can abandon revisions that have descendants, in which case all the descendants will be rebased onto the parent. This will be as if the commit you’ve abandoned never existed. Personally, I’ve never had to abandon an intermediate commit, but it’s good to know that it’s possible.

Honourable mention

Before closing, I wanted to make sure I talked about the following commands which I’ve fished out of the long tail. Although used less often, I think they’re worth discussing because they are useful in error recovery or during setup.

jj mine

This is another alias, defined as

[aliases]
mine = ["bookmark", "list", "-r", "mine()"]

jj mine prints out the bookmarks/branches that I am currently working on, when I want to take stock and switch between them.

jj undo

Short for jj operation undo, jj undo is a lifesaver! I cannot think of an equivalent for git although having ohshitgit.com on your browser bookmarks toolbar comes close.

Exactly what it says on the tin, jj undo will reverse the last operation you performed, allowing you to recover from mistakes or unexpected behaviour.

As you can see, I don’t have much reason to call this function, but knowing that it’s there makes me breathe easier.

jj git init --colocate

This is the command that gets you started by transforming a local git repository into a jj repository. If you have read this far, you’re obviously quite interested in how jj does things.

The best way to learn how to use jj is through doing. Let this be the sign you’ve been waiting for, and give jj a try!