Jujutsu VCS 101
Git can sometimes be a bit of a pain, and often times it feels like we have to deal with its imperfections. With Jujutsu, that is no longer the case.
In the world of programming, there is no tool more ubiquitous than Git—the undisputed standard for version control systems. From the baby steps of initializing a repository and making a basic commit to the more senior maneuvers of cherry-picking and handling the dreaded merge conflict, working with Git and its commands is an experience shared by nearly every developer.
Given how deeply embedded Git is in the developer experience, replacing it in my own workflow was never something I had seriously considered. I forever thought git commit -m "some helpful message" would be the way. But Jujutsu's promise of a more intuitive approach to version control piqued my interest enough that I recently began daily-driving it. Of course, adopting Jujutsu meant first stumbling through its unfamiliar commands and concepts, just as I had when learning Git for the first time. However, from what I remember of that experience, Jujutsu's initial learning curve felt considerably steeper—not necessarily because Jujutsu was more complicated, but because I had to rework years of version control habits and unlearn some concepts that had become second-nature. I often found myself tempted to abandon the journey and resort to the old reliable workflow I had formed with Git.
But after spending some more time with it, I slowly found myself becoming an advocate for the Git alternative. And I started to wonder whether Git's dominance comes not from being the best version control system, but simply from being the one developers already know. Is Git still king because its model is inherently better, or because years of familiarity have made every alternative feel more difficult by comparison? Now, I do not intend to come to a definitive answer, but rather, to encourage you to try it out to determine your own answer. However, in order to do so, you first need to understand what is Jujutsu.
What is Jujutsu #
Jujutsu, commonly shortened to jj, is a version control system designed to sand down many of the rough edges that come with using Git. Its interface is separate from its storage layer, allowing it to work with different backends, including Git. But regardless of which backend it uses, jj presents a fundamentally different model for handling work and history than the one most developers are familiar with.
In Git, everything centers around the "commit". You modify files in your working tree, choose which changes to stage, and then package those staged changes into a commit that represents a fixed snapshot in the repository's history, which can be identified by its unique hash. Now, the thing with commits is that you are commiting. Git commits are immutable, so to edit one, you have to destroy it and recreate it, generating a new hash along with every descendant commit.
In jj, the mental model is instead a "change". Under the hood, a change ultimately operates similarly to a commit, meaning that a change and commit are somewhat interchangeable. However, when using jj, a change specifically represents an evolving piece of work rather than a finalized snapshot. As you continue editing, jj automatically updates the current change, preserving its identity, even as the underlying commit is rewritten. This means that the commit hash may change, but the change ID that jj uses to identify that moment in history never changes, giving developers a persistent reference to the work itself.
Change IDs #
The difference between a commit and a change becomes visible when running jj status, which would give you an output that resembles the following:
Each revision is displayed with two identifiers. The first yqttnwyq (or its unique prefix yqt) is the change ID, while the second 0a72288d is the underlying commit ID. The @ symbol marks the change currently being edited, or in other words, the working-copy commit. The @- symbol refers to its parent. The same information appears in jj log, which displays repository's history as a graph of changes similar to the following:
@ tqoqyzux de9cdf2c Fix confirmation flow
○ wukuqkvr d1caeb26 Add Slack reminders
◆ urqyoqnm 81d5b52c main
Change IDs can be used anywhere jj expects a revision, which makes them the primary way to interact with a piece of work as it evolves. For example:
jj show ym
jj diff -r ym
jj edit ym
jj describe -r ym
jj show ymdisplays the change's metadata and full diff.jj diff -r ymshows only the changes it introduces relative to its parent.jj edit ymmakes that change the working-copy commit so you can continue modifying it directlyjj describe -r ymlets you update its description (similar to a commit message)
Bookmarks #
Since changes can be referenced directly by their change IDs, Jujutsu does not rely on branches to organize work in quite the same manner Git does. Instead, it uses named pointers to specific revisions called bookmarks. Creating a new change from a bookmark does not automatically move the bookmark forward, as your work remains attached to the change itself. When the change is ready, you can move the bookmark to it with jj bookmark set feature-name -r @ and publish it with jj git push --bookmark feature-name
This differs from Git, where checking out a branch makes that branch the active line of development and new commits automatically advance it. With this change in jj, it is possible to easily move between and rewrite changes without constantly managing branch state.
Revsets #
Throughout the previous sections, we have already been using one of Jujutsu's core features: revsets. A revset is an expression that identifies one or more revisions. It can be as simple as a change ID such as ym, a bookmark like main, or a symbol such as @ for the working-copy commit. Since most jj commands accept revsets, the same language is used consistently whether you are viewing history, inspecting a diff, or rewriting changes.
Where revsets become especially useful is in their ability to describe entire groups of revisions. Expressions can select revisions according to their relationships or properties and combine those conditions into more precise queries. For example, mine() selects changes authored by you, mutable() selects changes that Jujutsu considers safe to rewrite, and mine() & mutable() selects revisions that satisfy both conditions. Another useful application of revsets is searching through change descriptions. Since description() accepts string patterns, you can use it to find old work even when you no longer remember its change ID:
jj log -r 'description(glob-i:"*slack*")'
This returns every revision whose description contains "slack". And just as before with combining mine() & mutable(), you can combine the description query into something more complex:
jj log -r 'mine() & mutable() & ~description(glob-i:"*wip*") & description(glob-i:"*slack*")'
With this command, you would be querying the entire jj log for revisions that were authored by you, are mutable, do not contain "wip" in the description and do contain "slack" in the description.
Now with the building blocks out of the way, what does using Jujutsu even look like?
How to Jujutsu #
As you might imagine, there are many different flows that you can do with Jujutsu. However, I am just going to cover just a few to serve as a kickstart in your jj journey
The Basics #
A normal Jujutsu workflow would resemble something as follows:
- Fetch the latest changes
- Create a new change
- Edit files
- Describe or commit the work
- Push it for review
Start by fetching the latest remote changes:
jj git fetch
Then create a new change on top of main:
jj new main@origin
At this point, you can make your changes, and remember, there is no staging area so no need to do git add. Once the change is complete, commit it:
jj commit -m "Add calendar navigation"
This describes the current change and creates a new empty working-copy commit on top of it. To push the work, create a bookmark and push it:
jj bookmark create rd/some-feature-branch -r @
jj git push --bookmark rd/some-feature-branch
However, rarely is work this linear. Commits are created in the wrong order, fixes belong in earlier changes, main moves while you are working, and rebases introduce conflicts. This is where Jujutsu shines.
Beyond the Basics #
While jj's conflict-resolution flow is not fundamentally different from Git's, it feels noticeably smoother.
In Git, a rebase pauses at each conflict and places you into a temporary rebase state. You resolve files, stage them, and run git rebase --continue before moving on. Jujutsu instead records the conflict directly in the affected change and lets the rest of the base finish. You can then return to that change, resolve it like any other edit, and continue working without a separate staging or continuation step.
Suppose you wanted to rebase a bookmark onto main:
jj rebase -b <feature-bookmark-id> -o main@origin
If the rebase creates a conflict, jj records it in the affected change rather than stopping the operation halfway through:
@ Add office picker
○ Add calendar toolbar
○ (conflict) Add calendar navigation
○ main@origin
You can then edit and resolve this change directly:
jj edit <calendar-navigation-change-id>
jj resolve
jj status
Alternatively, you can create a new child change on top of the conflicted one:
jj new <calenedar-navigation-change-id>
jj resolve
That records the conflicted resolution in a separate commit:
@ Resolve calendar navigation conflict
○ (conflict) Add calendar navigation
○ main@origin
You would then squash the resolution into the conflicted parent:
jj squash
As you can see, the commands themselves are not necessarily revolutionary. In the end, both jj and Git can rebase commits, resolve conflicts, and rewrite history. But what makes Jujutsu appealing is that these operations feel more consistent with the rest of its workflow. Rather than switching into a special mode whenever history needs to change, you continue working with the same changes, identifiers, and commands you were already using. I only scratched jj's surface, missing out on notable commands like jj undo, but hopefully this gives you enough of a foundation to try Jujutsu in your workflow.