After a Decade with Git, I Tried Jujutsu (jj). Here's What I Think
I’ve been using Git for over a decade. It’s so ingrained in my workflow that I barely even think about it anymore—the commands are pure muscle memory. And recently, that started to feel like a problem. Routine can be the enemy of improvement, and I wondered if I was sticking with my workflow out of habit, not because it was the best one for me.
So, I decided to roam a bit and try something new: Jujutsu (jj). I’d seen it mentioned, and the promise of a simpler, more intuitive Git experience was too intriguing to ignore.
A New UI for an Old Engine
What clicked for me right away is that jj
isn’t trying to be a new VCS from scratch. It’s a smarter UI for the Git backend I already have. The setup reflects this perfectly; you don’t abandon your Git repo, you just... co-locate jj
on top of it.
You can start in an existing project: λ jj git init --colocate
Or clone a repo and have jj
manage it from the start: λ jj git clone git@github.com:your/repo.git --colocate
The --colocate
flag tells jj
to live alongside your .git
directory. All your history is still there, Git-compatible, but you get to interact with it through a different lens.
Escaping the Staging Area
The biggest mental shift from Git is that Jujutsu has no staging area.
With Git, my process was a constant, meticulous dance of git add -p
, carefully selecting lines to craft the perfect commit. jj
throws that entire ceremony out the window. Your working copy is always part of a special, mutable “working-copy commit.” You just code.
When you’re ready, you simply describe the work you’ve done:
λ jj describe -m "A clear and concise commit message"
This command applies the message to your current block of changes. To start a new set of changes, you run jj new
, which leaves your previous work as a normal commit and gives you a fresh working copy commit. The first time I fixed a typo in a commit from five minutes ago by simply changing the file and running jj describe
on that same commit again, without a multi-step interactive rebase, was a real eye-opener. It just worked.
Most of the time, I use a simple alias, jj c
, which combines describing and creating a new commit. The only muscle memory I had to fight was the instinct to git add
. But once I let go, the workflow felt surprisingly liberating. When it’s time to share, you still use a familiar command:
λ jj git push
revsets
: A Query Language for Your Code History
While the simplified commit model is refreshing, the true power of jj
became apparent when I started using revsets
.
revsets
are a query language for your commit history. It lets you select and operate on commits with a logic that feels miles ahead of what branch names and commit hashes alone can offer. This is where jj
starts to feel less like a tool and more like a superpower.
For example, I set up some aliases in my jj
config to manage work, I’m not ready to push:
[revset-aliases]
'wip()' = 'description(glob:"wip*")'
'private()' = 'description(glob:"private:*")'
'excl()' = 'wip() | private()'
Now, if I start my commit messages with wip:
or private:
, I can easily push everything except those changes:
λ jj git push --change 'all() - excl()'
This level of control, built right into the tool, removes a layer of anxiety I didn’t know I had about managing stacked changes. It reframes your commit history from a simple timeline into a queryable database.
So, Did I Switch?
Have I uninstalled Git and gone all-in on jj
? No. The reality is that Git is the lingua franca of our industry.
But jj
has earned a permanent place in my workflow as my day-to-day driver. It lets me think about my code, not about wrestling with the staging area. I spend less time fighting my tools and focusing more on the work itself.
If your own Git habits feel more like ritual than intent, I highly recommend giving jj
a look. It’s a well-crafted reminder that even the most fundamental parts of our toolkit are worth re-examining.