Jujutsu (jj)
A new front-end for your existing Git repos. Built at Google. Same .git folder, same remotes, your teammates don't have to switch — but the model under your hands is radically different. Whether that's a gift or a tax depends on how much you rewrite history.
git add, no "did I stash that?"jj undo reverses anything.The rebase race
You forked 3 commits off main. Someone landed 3 more commits to main while you were working, and two of yours will conflict with the new tip. Hit play on both.
Git
jj
Note the difference in stops. Git halts the operation on each conflict; you must resolve --continue before any other work. jj stores the conflict inside the commit and keeps going — you can leave it conflicted overnight if you want, and rebase or branch off it in the meantime.
The five mental-model shifts
If you only learn five things, learn these. Everything else in jj falls out of them.
The working copy is a commit (@)
Every file change is automatically snapshotted into the commit at the head of your current "change." There's no untracked/staged/committed three-state limbo.
Working tree → index → HEAD. Three states, two boundaries (add, commit) you must cross.
Working tree is a commit called @. Edits update it in place on every jj invocation.
No index. Just split and squash
You don't pre-stage hunks. You commit everything, then sculpt: split a commit in two, squash work into an earlier one, move hunks between adjacent commits.
git add -p, reset, commit --amend, rebase -i — different verbs for related ideas.
jj split, jj squash, jj squash -i. Same verbs work whether the target is your latest commit or one ten back.
Descendants auto-rebase
Edit a commit deep in the stack and every child rebases automatically. Stacked-PR workflows stop being a chore.
Rewrite an ancestor → orphan all descendants →git rebase --onto dance to reattach them.
Edit ancestor → children move with it. If conflicts arise, they're committed and you fix them when convenient.
Conflicts are first-class commit content
A merge or rebase with conflicts doesn't halt the operation. The conflict markers are stored in the commit and propagate down the stack. You resolve at your leisure.
Conflict → repo in limbo state → must resolve before any other operation. Conflict → committed as a conflicted commit → continue working, rebase other things, resolve when you choose.Operation log = total undo
jj op log records every state-changing operation across all refs atomically. jj undo walks it backwards. Lost a branch? Bad rebase? One command.
git reflog, find the SHA, reset --hard. Sometimes also fsck --lost-found.
jj undo. That's it. Repeat to keep undoing. jj op restore <id> for surgical rewinds.
Branches are optional ("bookmarks")
jj tracks every head of the commit graph automatically. You don't have to name a line of work to keep it alive. Names ("bookmarks") exist when you need to push or share — not as scaffolding to remember what you were doing.
Need a branch to keep work findable. Detached HEAD = warning sign. Anonymous heads are normal. Slap a bookmark on it only when you're ready to push.The working copy, visualized
Git's three-state model vs jj's one-state model. Click "Edit file" and walk through what each tool needs from you to durably capture that edit.
@ the commit you're inSteps to common tasks
How many discrete commands each tool needs for typical history-shaping work. Bars scroll into view and fill on first sight.
Same task, both tools
Pick a workflow. The git column is what most teams actually type; the jj column is the same intent expressed in jj's primitives.
# find the commit, mark for fixup git log --oneline # edit files... git add -p git commit --fixup=abc123 git rebase -i --autosquash main # resolve any conflicts, --continue x3
# edit files, then: jj squash --into abc123 # descendants auto-rebase # if conflict: keep working, fix later
Cheat sheet
| Intent | Git | jj |
|---|---|---|
| Start working on something new | git checkout -b feat | jj new |
| Save current state | git add . && git commit -m | nothing — auto-snapshotted; describe later with jj describe |
| See history | git log --graph | jj log |
| Edit an old commit's message | git rebase -i <sha>^ | jj describe -r <rev> |
| Move work onto latest main | git rebase main | jj rebase -d main |
| Combine two commits | git rebase -i (squash) | jj squash |
| Split a commit | edit-rebase dance | jj split |
| Undo last operation | git reflog + reset --hard | jj undo |
| Push to GitHub | git push origin feat | jj bookmark set feat -r @ then jj git push |
When not to use jj
The honest part. jj is pre-1.0, the CLI is still evolving, and entire ecosystems (GUIs, hooks, hosted-service integrations) assume git. Here's where the friction outweighs the wins.
You don't rewrite history
If your workflow is "commit, push, merge, never look back," 70% of jj's value evaporates. The win is in sculpting commits — split, squash, reorder, restack. Linear-only workflows get a marginal upgrade at best.
You live in a Git GUI (Sourcetree, Tower, JetBrains, GitKraken)
Almost no GUI speaks jj natively yet. You'd be dropping to a CLI for the operations you used to click, and the underlying model is different enough that "just use the git view" lies to you about state.
Your team has git hooks doing real work
jj's git-backend operations can trigger hooks, but the integration is incomplete and varies by hook type. Mission-critical pre-commit / pre-push automation, signed commits, and gpg-sign requirements still have rough edges — verify before committing your team's flow to it.
Heavy Git LFS, submodules, or partial-clone needs
Support is improving but historically weaker than git itself. If your repo's main pain point is one of these, jj is solving the wrong problem for you.
You're on Windows with SSH-only remotes via libgit2
The bundled libgit2 SSH transport has known gaps. Workable — but expect to configure ssh-agent and possibly fall back to the system git binary for pushes.
You're shipping to production tomorrow
jj is stable enough for daily use (Google uses it internally, ~30k stars, active dev), but it isn't 1.0. CLI flags shift between releases. If you can't tolerate occasional "what changed in jj 0.X?" troubleshooting, wait.
You're learning git for the first time
Counterintuitive, but: jj's abstractions are cleaner, yet the world runs on git. Learning jj first leaves you unable to debug a CI snag, read a Stack Overflow answer, or pair-program with anyone using git. Learn git's mental model first, then graduate to jj if you want.
So, should I switch?
✓ Yes, probably
- You routinely have 3+ commits stacked and rebase them onto main.
- You use
git rebase -iat least once a week. - You've ever lost work and dug through reflog.
- You use ghstack, graphite, or hand-rolled stacked-PR tools.
- You like trying new tools and reverting on a whim — jj coexists with git on the same repo, so the cost of trying is genuinely low.
✗ Not yet
- Your team standardizes on a Git GUI and you can't deviate.
- Your repo depends on submodules / LFS / commit signing.
- You don't rewrite history and don't want to start.
- You're still building muscle memory for git itself.
- You're allergic to running pre-1.0 software on serious work.
The tl;dr: jj isn't "git but better" in the abstract — it's git but better for the specific workflows where git is worst. If those workflows are your daily life, the upgrade is dramatic. If they aren't, you'll learn a new tool to do the same thing.