If you don't like Jujutsu, you're wrong
As all developers, I’ve been using git since the dawn of time, since its commands were an inscrutable jumble of ill-fitting incantations, and it has remained this way until today. Needless to say, I just don’t get git. I never got it, even though I’ve read a bunch of stuff on how it represents things internally. I’ve been using it for years knowing what a few commands do, and whenever it gets into a weird state because I fat-fingered something, I have my trusty alias, fuckgit, that deletes the .git directory, clones the repo again into a temp folder, and moves the .git directory from that into my directory, and I’ve managed to eke out a living for my family this way.
Over the past few years, I’ve been seeing people rave about Jujutsu, and I always wanted to try it, but it never seemed worth the trouble, even though I hate git. I idly read a few tutorials, trying to understand how it works, but in the end I decided it wasn’t for me.
One day I randomly decided to try again, but this time I asked Claude how to do with Jujutsu whatever operation I wanted to do with git. That’s when the mental model of jj clicked for me, and I finally understood everything, including how git works. I never thought a VCS would spark joy in me, but here we are, and I figured maybe I can write something that will make jj click for you as well.
It also doesn’t hurt that Jujutsu is completely interoperable with git (and thus with providers like GitHub), and I can have all the power of Jujutsu locally on my git repos, without anyone knowing I’m not actually using git.
The problem
The problem I had with the other tutorials, without realizing it, was that there was a fundamental tension between two basic things: The best way to explain jj to someone who knows git is to use all the git terms they already know (because that makes it easy for them), but also to tell them to think about the git terms they know differently (because otherwise they’ll form the wrong mental model). You can’t really explain something by saying “a jj commit is like a git commit, except where it’s not”, so I’ll try to do things a bit differently.
This will be a short post (or, at least, not as long as other jj tutorials), I’ll explain the high-level mental model you should have, and then give a FAQ for how to do various git things with jj.
Warnings
Just a disclaimer before we start, this is going to be far from an exhaustive reference. I’m not an expert in either git or Jujutsu, but I know enough to hopefully make jj click for you enough to learn the rest on your own, so don’t be too annoyed if I omit something.
Also, you’re going to read here some things about the way Jujutsu likes doing things that will offend you to your very core, and your first reaction will be “madness, this cannot possibly work”. When you think this, I want you to relax, it’s fine, it does work, it just means I haven’t managed to make the whole thing click together for you yet. Just read on.
I’m not going to show you any Jujutsu commands here. I might refer to them by name, but I want you to understand the mental model enough to go look stuff up on your own, Jujutsu only has, like, three commands you’re going to use for everything anyway (yes, you can do everything you do with git with them).
(By the way, if you’re going to be trying things out while reading this post, definitely get jjui, it lets you visually work with the repository in a way that makes everything much easier to understand.)
The high-level mental model you should have
First of all, all the basic git things you’re already familiar with are there in jj: Commits, branches, operations on those, all those things carry over, with some small differences.
The main difference is in the general way the two work, jj simplifies git’s model a lot by getting rid of some inconsistencies, and makes it much easier to understand what’s going on “under the hood”, because the “under the hood” is now so much smaller and simpler, that it can just be over the hood.
git
The mental model that you probably have with git is something like an assembly line. You take a bunch of components, you form them into a widget, you put the widget into a box, you write “General bug fixes” onto the box, seal it, and send it off, never to be seen again by anyone.
That’s what git thinks of as a commit. You have some work that is The Thing You’re Working On Now, and then at some point that’s kind of done, you select which pieces of that work you want to immortalize, and you commit them, freezing them in time forever from then on. (I know you can edit commits, but this is largely git’s mental model, commits are immutable).
Jujutsu
Jujutsu, in contrast, is more like playing with Play-Doh. You take a lump, cut it into two, shape one piece into something, give it a name, change your mind, give it another name, take a bit of the second piece and stick it on the first piece, and generally go back and forth all around your play area, making changes.
Jujutsu wants you to be able to go back to an old commit, change it (gasp!), go to another branch (three commits back from that HEAD), change that commit too, move whole branches of your tree to other parts of it, whatever you want. Your worktree in Jujutsu is a free-for-all where you can rearrange things as you like.
Madness, this cannot possibly work
Yes yes, nobody wants their commits changing from under them, that’s why Jujutsu doesn’t let you easily change commits that have been pushed to a remote, you can relax now.
However, if you spend a moment thinking about what I said above, you’ll probably realize that a few things need to be different from git for this to work (and they are):
Commits have to be mutable.
Indeed, Jujutsu commits are mutable (until you push them). Right now you’re thinking of commits as something that can’t change, but this is one of the things you need to accept. You can (and will) go back to a previous commit (that you haven’t yet pushed) to fix a bug in it that you just hit, and it’s as simple as checking out (jj calls it editing) that commit and making the change. You don’t have to commit again! Jujutsu does whatever it needs to do under the hood when you run the jj command, to you it just looks like your edits are automatically persisted in the commit, in real time.
To clarify, Jujutsu doesn’t create new commits while this goes on, you just see one “open” commit that you keep making changes to your code in.
If I can just go into a commit and edit it and jj auto-saves, there must not be a staging area.
Indeed, there is no staging area like git has. git splits code to either be in the repo (in a commit), or outside it (staged/unstaged).
Jujutsu doesn’t have that, you are always in a commit. This is important: In git, you’re outside a commit until you create one. In Jujutsu, you are always inside a commit. Nothing is ever outside a commit, “outside a commit” isn’t a thing in Jujutsu.
Even the very commit command in Jujutsu is an alias that adds a message to the commit you’re on, and then creates a new (empty) one that you’ll now be working on. Even when you create a new repo, you start in a commit.
This is the most important difference between jj and git, and the one thing you should think a bit about, as it enables many really interesting workflows.
Always being in a commit means that yes, you will have commits that are half-finished work. Maybe lots of them! I usually indicate this in the commit message, to remind myself.
So commits might not have a commit message?
You are impressively perceptive for a hypothetical straw man in whose mouth I’m putting words. Exactly, commits might not have a commit message. They start out blank, and you can add a commit message at any point, whenever you have an idea of what that commit will do. It might be when you start working on it, it might be half-way through, or it might be at the end. Personally, I usually add the message at the end, but that’s just preference.
So there’s no stashing either?
Yes, since everything is always in a commit, there’s nothing to stash.
In git, if you have some uncommitted changes and want to check out an old commit, you need to stash them first. In Jujutsu, since all your changes are automatically persisted in a commit at all times, you can have some new changes (which, if this were git, would be uncommitted), you can check out (or edit) an older commit, then come back to your new changes in the latest commit, and they’ll all be there.
But then branches need to be lightweight.
If you’re going to be jumping around the tree all the time, making commits and branches, they can’t require names. Jujutsu lets you create branches by just creating a commit, you don’t need to name the branch. In Jujutsu (and in git!), branches are simply two or more commits with the same parent, it’s just that git artificially makes you think of branches as special, because it makes you name them.
In Jujutsu, creating a branch is as simple as checking out the commit you want to branch from, and creating a new commit on top of it. This is one thing Jujutsu simplifies over git. In git, branches are a fairly heavy thing, you have to name them, you have the mental model of “being” on the branch, and your workflow is centered around them. In Jujutsu, you just… add a new commit, and if that commit has siblings, well, that’s now a branch.
Conflicts
I haven’t talked about conflicts much, because, unlike git, in practice they haven’t really been anything special. Jujutsu doesn’t stop the world at all, it doesn’t even particularly complain, it just marks a commit as conflicted, but you can continue working on other places in the worktree and then later come back at your leisure and fix that commit’s conflicts!
Whereas in git you have to quit what you’re doing and fix the conflicts right now, jj is more “by the way, when you have some time, let me know what this commit should look like*. The changes also cascade to all subsequent commits, which is fantastic. You only fix conflicts once, and jj takes care of the rest.
Snapshots
Under the hood, jj automatically and transparently commits whatever you’re working on when you invoke the jj command (it can also be configured to do it on its own whenever a file in the repo changes). This is safe, as these intermediate changes won’t be pushed anywhere, but this means that you get snapshots for free!.
If you’ve ever had Claude get to a working solution, but then trip over itself and mess it up, jj can help, you can use the oplog to go back to the way your repo looked a few minutes ago, even if you didn’t explicitly commit anything! Even using the status or log command to look at stuff will take a snapshot of your repo, allowing you to return to it if something goes wrong. No more losing unstaged changes, ever!
This has saved my ass a few times already.
Questions and answers
By now you probably have lots of questions, I’ll try to answer some of them here. If you have more questions, just send them to me and I’ll add them here, along with the answer.
How do I branch off main?
You don’t really branch off main, in that you usually won’t need to create two commits off main, you’ll only create one.
git
In git, we branch off of main, and now our mental model is that “we’re in that branch”. In reality, if you look at the graph on the right, it’s all still just a line, we’ve just made a mental “bend” in the graph to tell ourselves that we’re on a branch.
As far as the graph is concerned, though, nothing special really actually happened, we just added more commits. The only real difference is that “main” stops at the third commit, whereas “my branch” stops at the sixth commit. Other than that, the entire history is just one line.
Jujutsu
This is how jj thinks of branches. The two graphs are equivalent, but you aren't anywhere special, `main` is just farther back.
Jujutsu, on the other hand, doesn’t care what you think. It only cares what parents, children, and siblings commits have.
There are two reasons you might want to branch:
- History legitimately diverges into multiple directions, or
- You want to communicate to other people (or to yourself) that this part of the history is different (e.g. it contains some feature). This is also the case when you want to create a new branch so you can open a PR for it.
To Jujutsu, this repo’s history is a straight line, so there is no actual “branching”. The only reason to have branches here is communication, so Jujutsu asks you to label the commits that you want on the branches yourself. You can see these tags on the example on the right, and it’s the same as the git example above. There are still three commits in main, and three more in my branch.
Jujutsu calls these labels “bookmarks”, and they correspond to whatever git uses to tag branches. Bookmarks are what you’ll tag your commits with to tell git what your branches are.
Continuing the earlier example, if we create a second commit off main, even if that’s a merge commit (a commit with two parents) that’s when the tree actually diverges. In the graph on the right, the commit where we branched off now is a parent to two commits, and history is no longer linear. This isn’t special, it’s just how things are, but this is what’s actually a real “branch” to Jujutsu.
The way that git does things, ie creating a branch without history actually diverging, is just for us humans and our communication needs.
Jujutsu doesn’t require you to name its branches. You can happily work without any branch names at all, and you can easily see what branch is for what from the commit descriptions. You can name them, if you prefer, but you don’t have to.
This sounds a bit alien right now, but it’s actually a really nice way to work.
I’m worried I’ve lost you here, but it doesn’t matter. You’ll understand all of this easily when you play around with the tree a bit in jjui.
How do I add a commit message?
You can add a commit message at any time to the current, using the describe command. You can do this at any time, you can even go back to other commits and amend their messages (again with the describe command).
How do I choose which of my changes to commit?
You don’t! Everything is already in a commit! What you do is you interactively select some of the changes in the current commit (whether this commit is blank/new or an old commit, it doesn’t matter), and you split that commit into two.
Jujutsu can also do this automatically! If you have a commit with a bunch of small changes to various files, jj can absorb these changes into the closest ancestor commit where each thing changed. This is pretty magical, as you can add a few one-liner bugfixes here and there, and jj will just automatically include them in the commits where those lines were touched.
How do I check out a commit?
Without getting too much into specifics, you just edit the commit you want. This checks it out and you can make changes to it, however keep in mind that, if the commit was previously pushed to a remote, jj will give you a warning that you shouldn’t change commits you’ve pushed.
jjui will make navigation around the repo really easy, so use it for checking out commits as well.
How do I cherry-pick a commit onto another branch?
You just… move it. In jjui, go to the commit you want to move, press r (for rebase), go to the commit you want to move it after, press enter, and that’s it.
How do I reset soft/hard?
There isn’t really a soft reset, as there isn’t a staging area for your changes to be reset in. Simply check out (edit) the commit you want to edit, that’s a soft reset in Jujutsu.
For a hard reset (ie to throw away a commit), you abandon that commit. jjui will, again, make it much easier to do this.
What if I make a mistake?
No matter what you do, you can undo it. Not just changes, but any jj operation, you can undo rebases, pulls, anything.
You can also use the oplog (again, jjui makes this really easy) to go back to how the whole repo looked at any point in time. Don’t be afraid to try things, with jj it’s really easy to undo any mistake.
How do I amend a commit?
Simply edit it and make the changes you want.
How do I move unstaged changes from one branch to another?
There are no unstaged changes in jj. All changes are in a commit, if you want to move the changes in your current commit to another branch, simply move your current commit to the target branch by rebasing. I can never remember what “rebase X onto Y” does, so just move the commit with your changes to be a child of your branch’s tip (again, use jjui for this).
How do I open a PR on GitHub?
To do that, you need to push a new branch. Go to the commit you want to push, then probably create a new one on top of that (I tend to create a new commit when I’m done with an old one, just so I’m remember I’m done, but this is personal preference). Then, bookmark that commit with the branch name you want to give your PR, and push the commit along with the bookmark.
That’s all, now you can open the PR.
Here, jj exposes the low-level operations much more than git: You need to move the bookmark on your own to the commit you want to push (git does that automatically for you), and you need to push the bookmark manually as well. This is very helpful for understanding how things work under the hood, but usually you’ll set a jj alias to do this in one step.
Personally, I have an alias (which I’ll include below) to find the bookmark name, move it to the latest commit, and push.
My aliases
Here’s my alias config:
This means I can jj init to add jj to a git repo, and jj cma "message" to describe the current commit and create a new one on top of it (that’s what commit does under the hood).
jj ps is a convenience alias that:
- Looks backward in history
- Finds the last bookmark there (if this were git, this would be my branch name)
- Checks if the current commit has changes in it
- If it does, it creates a new commit
- Moves the bookmark to the parent commit (the one I was on before I ran the command)
- Fetches changes from upstream (to update my tree)
- Pushes the changes to the remote
I use this a lot!
Epilogue
Jujutsu doesn’t do anything that git can’t do, but it removes so much friction that you’ll actually end up doing things all the time that git could do, but that were so fiddly with git that you never actually did them.
Creating a branch for a minute just to try an idea out even though you’re in the middle of some changes, going back to a previous commit to add a line you forgot, moving commits around the tree, all of these things are so easy that they’re now actually your everyday workflow.
With git, I never used to switch branches in the middle of work, because I was too worried that stashing multiple things onto the stack would eat my work. I’d never go back to a previous commit and amend it, because here be dragons. I was extremely afraid of rebasing because I always got one conflict per commit and had to unconflict the same thing fifty times.
Jujutsu gives you the confidence and understanding to do all of these things, and if you fuck something up (which I haven’t yet, miraculously!) the oplog is right there to fix everything to how it was 30 seconds ago.
I hope this tutorial made sense, but I’m worried it didn’t. Please contact me on Twitter or Bluesky, or email me directly, if you have feedback or corrections.