Jujutsu destroyed my Git workflow, and I'm not even mad!
I had everything and Jujutsu destroyed it. I had the perfect Git workflow with
lazygit perfectly set up, aliases for the most used commands like gst for
git status, gp for git push, and gloga for git log --oneline --decorate --graph --all. I used fixup commits and git
absorb to automatically create them.
I loved the feeling of a successful Git rebase and force push to my feature
branch after discovering git rerere. It was all so comfy. Now with Jujutsu, none
of that is useful anymore, and I’m not even mad!
What’s Jujutsu?
Jujutsu is a new version control system, mostly
referred to as jj. You can use it like any other VCS to track changes in text
files and collaborate with others. What sets jj apart is how you interact
with it and the fact that it abstracts the storage engine from everything else.
You can use Git as its backend and easily adopt it in existing codebases
without requiring everyone to switch tools.
Say you’re juggling two features. In Git, you either commit half-baked work
with a “WIP” message you’ll have to clean up later, or you stash and pray. And
when you spot a typo three commits back? That’s git commit --fixup, then
later git rebase -i --autosquash, carefully checking you didn’t mess up the
order, resolving any conflicts, force pushing your branch. Rinse, repeat. In
jj, your work-in-progress is already a commit, always. Switching to another
feature is just jj edit other-feature. No stashing, no throwaway commits.
Spot that typo three commits back? jj edit that commit, fix it, done. Every
descendant rebases automatically.
In jj, there’s no staging area. No git add. Everything is always recorded
automatically, and you can easily switch between working trees. No more need
for git stash or git worktree gymnastics.
Messed up an operation? Don’t worry, jj undo will revert it. The entire
operation history is tracked, so you can always go back. Like reflog, but better.
Learning jj
To be honest, at first, I wasn’t really impressed. I could see its potential but still didn’t believe the hype. Some operations felt unintuitive, or better, unfamiliar, which introduced some friction. It took me about a week of daily usage to get comfortable with it.
The moment it clicked was when I realized I could just be anywhere in my
history. Need to check something three commits back? jj edit there, poke
around, make a change if needed, and descendants rebase automatically. No
detached HEAD warnings, no “you have unstaged changes” blocking you. The
working copy is always a commit, so there’s nothing to lose.
The second realization: I stopped rebasing manually. In Git, I’d carefully
orchestrate rebases, sometimes multiple times a day, especially when stacking
changes. In jj, you just… work. Move commits around, and everything
downstream adjusts. It sounds small until you notice you haven’t typed rebase
in two weeks.
By the second week, I was all over it and navigating the repo felt like a breeze. Now Git feels kinda clunky.
I still reach for lazygit occasionally, mostly for exploring history, creating
tags, or just poking around the repo. But the day-to-day work? Completely done
in jj now.
What helped me most when switching over was this absolute lifesaver: the Git
command table. It maps Git
commands to their jj equivalents. Bookmark it.
Should you switch?
Here’s the catch: if you’re still getting comfortable with Git, I can’t
recommend switching to jj just yet. Git is the industry standard. You’ll find
it everywhere, and you’ll be expected to know your way around it. Learn Git
first, then come back to jj when you’re ready.
For everyone else who’s been living in Git for years and is curious about something better? Give it a shot. The tutorial is solid, and there are plenty of YouTube videos walking through real workflows.
One genuine annoyance for tmux users: if you navigate panes with
j/k, typing jj right after switching can misfire. The j gets eaten by
tmux’s repeat window. I’m still hunting for a clean fix.
My config
Here’s what I’m running if you want a starting point:
1#:schema https://jj-vcs.github.io/jj/latest/config-schema.json
2[user]
3name = "************"
4email = "************"
5
6[ui]
7editor = "nvim"
8merge-editor = "bc"
9paginate = "never"
10
11[aliases]
12tug = ["bookmark", "move", "--from", "heads(::@- & bookmarks())", "--to", "@-"]
13l = ["log", "--limit", "20"]
14la = ["log", "-r", "all()"]
15
16[merge-tools.bc]
17program = "bcomp"
18edit-args = ["$left", "$right", "-ro1", "-expandall"]
19merge-args = ["$left", "$right", "$base", "$output", "-automerge", "-reviewconflicts"]
20
21[merge-tools.mergiraf]
22program = "mergiraf"
23merge-args = ["merge", "$base", "$left", "$right", "-o", "$output", "-l", "$marker_length", "--fast"]
24merge-conflict-exit-codes = [1]
The tug alias is stolen from the community and makes bookmark management a
breeze. Beyond Compare (bc) handles merges, and
mergiraf is there for structured merge support.
That’s it. If you’re curious, the
tutorial is solid, and Chris
Krycho’s jj init essay captures
the “aha” moments better than I could. Maybe you’ll let jj destroy your
workflow too.