VERSION CONTROL · GIT-COMPATIBLE

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.

No staging area
Your working copy is a commit.
Every save is already tracked. No git add, no "did I stash that?"
Universal undo
jj undo reverses anything.
Rebases, squashes, branch moves, abandons — all atomic, all reversible from one log.
Conflicts are data
Commit broken merges.
Conflicts ride along through rebases until you have time to resolve them.

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

M0 M1 M2 M3 F1 conflict F2 conflict F3 conflict
Commands0
Stops0
Conflicts0

jj

M0 M1 M2 M3 F1 conflict F2 conflict F3 conflict
Commands0
Stops0
Conflicts0

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.

01

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.
02

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.
03

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.
04

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.
05

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.

Per-ref reflogs. 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.
06

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.

git actions: 0 jj actions: 0
Git — three states, two boundaries
Working tree
foo.py
git add
Index (staging)
foo.py
git commit
HEAD (history)
foo.py
jj — your working copy is a commit
@  the commit you're in
foo.py ✓ snapshotted automatically

Steps to common tasks

How many discrete commands each tool needs for typical history-shaping work. Bars scroll into view and fill on first sight.

Git
jj
Amend a commit 5 entries back in your stack
git
6 cmds
jj
1 cmd
Split a commit into two
git
6 cmds
jj
1 cmd
Rebase a 3-commit stack onto updated main (no conflicts)
git
4 cmds
jj
2 cmds
Rebase same stack (2 conflicting commits)
git
8 cmds + 2 halts
jj
2 cmds + 0 halts
Undo the last operation (multi-ref)
git
~4 cmds, error-prone
jj
1 cmd: jj undo
Capture a save (one file edit)
git
3 cmds
jj
0 cmds

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.

Git friction: high
# 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
If the fixup is 5 commits back and the stack has conflicts, this is a 10-minute ordeal.
jj friction: low
# edit files, then:
jj squash --into abc123
# descendants auto-rebase
# if conflict: keep working, fix later
One command. The conflict (if any) is now sitting in a commit you can fix on your own schedule.

Cheat sheet

IntentGitjj
Start working on something newgit checkout -b featjj new
Save current stategit add . && git commit -mnothing — auto-snapshotted; describe later with jj describe
See historygit log --graphjj log
Edit an old commit's messagegit rebase -i <sha>^jj describe -r <rev>
Move work onto latest maingit rebase mainjj rebase -d main
Combine two commitsgit rebase -i (squash)jj squash
Split a commitedit-rebase dancejj split
Undo last operationgit reflog + reset --hardjj undo
Push to GitHubgit push origin featjj 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 -i at 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.