Jujutsu (jj): The Git-Compatible VCS That Removes the Friction You Stopped Noticing
Jujutsu keeps Git's storage and network model and replaces the part you touch every day: no staging area, no stash, undo for every operation, conflicts that don't stop a rebase, and stacked PRs that rebase themselves. Here is the mental model, a hands-on stacked-PR flow, a Git-to-jj cheat sheet, and where it still falls short.
The friction you stopped noticing
Most senior engineers can rebase, cherry-pick and recover from a detached HEAD without thinking. That is exactly the problem. We stop noticing how much ceremony sits between an idea and a clean commit.
Consider a normal afternoon. You are halfway through a feature when a reviewer asks for a fix on the previous PR. You git stash, check out the other branch, amend, force-push, check out the feature branch, rebase it onto the amended commit, resolve the same conflict twice, and git stash pop. Then you discover the stash had an untracked file you forgot to include.
None of this is hard for an experienced engineer. All of it is overhead. Jujutsu (jj) is a version control system built on the premise that most of this overhead is a design choice, not a law of nature. This article covers what changes, what improves, and where it still falls short.
What jj actually is
Jujutsu is an open-source VCS started by Martin von Zweigbergk at Google, written in Rust and developed in the open at github.com/jj-vcs/jj. Its key architectural decision: the storage layer is pluggable, and the default backend is Git.
jj is no longer a fringe experiment. Thoughtworks placed it in the Assess ring of its Technology Radar Vol. 32 (April 2025), and in early 2026 it came up repeatedly in LWN's coverage of Git's own roadmap, where readers discussed Git borrowing ideas that jj popularized.
In practice, a jj repository is a Git repository. Commits are real Git commits, GitHub and GitLab see nothing unusual, and CI pipelines keep working. Current releases colocate repos by default: the .git directory and the .jj directory live side by side, so you can run git and jj commands in the same working tree.
The consequence matters for adoption: you can switch alone. Your team does not need to agree, your platform team does not need to approve a migration, and you can stop using jj at any moment by deleting the .jj directory.
The mental model shift
The commands are easy to learn. The model behind them takes a day or two, and it is where all the benefits come from.
Your working copy is a commit. In Git, uncommitted changes live in a limbo of working tree plus index. In jj, the working copy is always a real commit, shown as @, and jj snapshots it automatically whenever you run a command. There is no staging area, no git add, and no stash. To start new work, you run jj new. To name what you are doing, you run jj describe -m "...", before or after writing the code.
Changes have stable identities. Every change gets a change ID in addition to its Git commit hash. When you amend or rebase, the hash changes but the change ID does not. You refer to work by change ID, so references stay valid as history moves.
Branches are optional. jj is happy with anonymous lines of work. Named pointers, called bookmarks, exist mainly so you can push to a remote. They map directly to Git branches on GitHub.
The net effect: in Git you manage state (index, stash, HEAD, branch). In jj you manage changes, and the tool handles the state.
Four benefits that change daily work
1. Undo for everything
jj records every operation in an operation log. Any rebase, squash, abandon or fetch can be reversed with one command.
jj op log # every operation, newest first
jj undo # reverse the last operation
jj op restore <op-id> # return the whole repo to an earlier state
Git has the reflog, but it tracks refs one at a time and assumes you know which ref to look at. The jj operation log covers the entire repository state. A botched interactive rebase stops being a 20-minute recovery exercise.
2. Conflicts that do not block you
In Git, a rebase with a conflict stops halfway and holds your repository hostage until you resolve or abort. In jj, conflicts are first-class data stored inside commits. The rebase completes, the conflicted commits are marked in jj log, and you resolve them when convenient.
jj rebase --onto main # completes even if conflicts appear
jj new <conflicted-change>
# fix the files, then
jj squash # fold the resolution into the conflicted change
Better still, once you resolve a conflict in one commit, descendants that inherited it are updated automatically.
3. Automatic rebase of descendants
Edit any commit in the middle of a stack, and every commit on top is rebased for you. This is the feature that makes stacked pull requests practical.
jj edit <change-id> # move into a commit lower in the stack
# make the reviewer's fix; descendants follow automatically
jj new <top-change> # return to the tip
The Git equivalent involves git rebase -i, fixup! commits, --autosquash, and often --update-refs.
4. Revsets: a query language for history
Revsets let you select commits with expressions instead of copying hashes.
jj log -r 'mine() & ~::main' # my commits not yet in main
jj log -r 'conflicts()' # every commit with an unresolved conflict
jj log -r 'description("JIRA-1234")' # commits mentioning a ticket
The same expressions work with rebase, abandon, squash and most other commands, so bulk history operations become one-liners.
Hands-on: a stacked change in an existing repo
Here is a realistic flow: two dependent changes, reviewed as two PRs, with a review fix on the first one.
Step 1. Enable jj in a repo you already have.
cd my-service
jj git init # adds .jj next to .git; nothing is rewritten
jj log # your Git history, rendered as a graph
Step 2. Build the stack. No branches yet, and no git add.
jj new main -m "Add retry policy to SQS consumer"
# edit code; jj snapshots it automatically
jj new -m "Emit retry metrics to CloudWatch"
# edit code for the second change
Step 3. Push both for review. The -c flag creates a bookmark (a Git branch) for each change.
jj git push -c @ # the retry metrics change (your working copy)
jj git push -c @- # the retry policy change below it
jj refuses to push a change that has no description. It is a small guardrail, but it stops anonymous work-in-progress commits from leaking into a PR.
Step 4. Address review feedback on the lower change.
jj edit <retry-policy-change-id>
# apply the fix; the metrics change is rebased on top automatically
jj git push --all # update both PRs
No stash, no checkout of another branch, no manual rebase of the second PR. If anything goes wrong, jj undo takes you back one step.
Beyond stacks: megamerges and parallel AI agents
Most introductions stop at stacked PRs. The two workflows below are where experienced jj users report the biggest gains, and neither is practical in plain Git.
The megamerge: work on everything at once
A megamerge is a local merge commit whose parents are all your active branches. You work in an empty change on top of it, so you always code against the combined state of your work and see cross-branch conflicts early. Then jj absorb routes each edited hunk back into the commit that last touched those lines.
jj new feat-retry feat-metrics docs-runbook # one merge, three parents
jj commit -m "megamerge" # keep it as a named, empty layer
# edit freely across all three areas
jj absorb # each hunk goes home to its branch
jj git push -c feat-retry # push branches, never the megamerge
Chris Krycho's write-up on megamerges and jj absorb is the best deep dive. The megamerge stays private; only the individual branches go to review.
Workspaces for parallel coding agents
jj workspace add is the jj equivalent of git worktree: several working directories backed by one repository store. This maps neatly onto running multiple AI coding agents in parallel, each in its own directory.
jj workspace add ../my-service-agent-auth
jj workspace add ../my-service-agent-tests
# run one agent per directory, then from the main workspace:
jj log # every agent's changes in one graph
jj squash --from <agent-change> # pull an agent's work into yours
Two jj properties make this safer than Git worktrees. Every agent action is a recorded operation, so jj op log shows what an agent did to history and jj undo reverses it. And because working copies are snapshotted automatically, an agent cannot lose uncommitted work by switching context. The community already ships helpers, such as plugins that make Claude Code's worktree isolation use jj workspaces.
Git to jj cheat sheet
| You want to | Git | jj |
|---|---|---|
| Start using jj in an existing repo | no equivalent | jj git init |
| See status | git status | jj st |
| See history | git log --graph | jj log |
| Start new work | git checkout -b feat | jj new main |
| Write a commit message | git commit -m | jj describe -m |
| Commit and start the next change | git add -A && git commit | jj commit -m |
| Amend the previous commit | git commit --amend | jj squash |
| Split a commit in two | git rebase -i + git reset -p | jj split |
| Route fixes into the right commits | fixup! commits + --autosquash | jj absorb |
| Edit an older commit | git rebase -i + edit | jj edit <change> |
| Drop a commit | git rebase -i + drop | jj abandon <change> |
| Rebase onto main | git rebase main | jj rebase --onto main |
| Stash work | git stash | not needed: jj new |
| Second working directory | git worktree add | jj workspace add |
| Undo the last operation | reflog spelunking | jj undo |
| Fetch and push | git fetch / git push | jj git fetch / jj git push |
| Create a branch | git branch name | jj bookmark create name |
Limitations and trade-offs
jj is not a free upgrade. Before adopting it, weigh these honestly.
- It is still pre-1.0. Releases ship roughly monthly under 0.x versions (0.40 landed in April 2026), and the CLI still changes between them. Flags like rebase -d were recently deprecated in favor of --onto. Breaking changes are documented in the changelog, but expect to adjust aliases and scripts occasionally.
- Git feature gaps. At the time of writing, Git submodules and Git LFS are not natively supported, and users report that .gitattributes line-ending rules (eol, text) are not honored. Repos that depend on either need a workaround or plain Git. Check the current docs before committing a team to it.
- Tooling lags behind Git. IDE integrations are thinner than the Git support built into VS Code and JetBrains. Community TUIs and extensions exist, but they are younger and less polished.
- Colocated repos can confuse Git-aware tools. jj often leaves Git in a detached HEAD state. Most tools cope, but some IDE Git panels and pre-commit hooks behave oddly.
- The learning curve is conceptual. The commands are simple. Unlearning the staging area and branch-first thinking takes a few days of deliberate practice.
- Enterprise governance. In regulated environments, a new developer tool may need security review before it touches company repositories. Since commits remain standard Git objects, signing and audit trails are unaffected, but the binary itself still needs approval.
There is also a principled counterargument. Skeptics in the LWN discussion argue that Git's state concepts exist for good reasons, that the reflog already covers most recovery, and that jj's simpler model makes some history constructions harder. Others point out that Mercurial's changeset evolution offered conflicts-as-data for a decade without winning. Both points deserve a fair hearing: jj's advantage is ergonomics on top of Git, not capabilities Git fundamentally lacks.
Verdict: who should try it now
Try it this week if you regularly split work into small reviewable PRs, rewrite history before review, juggle several lines of work at once, or run more than one coding agent against the same repo. The automatic rebase and the operation log alone repay the learning curve.
Wait if your repos depend on submodules or LFS, your workflow is a single long-lived branch with merge commits, or your organization requires approval for every developer tool.
The adoption path is low-risk by design:
- Install jj and run
jj git initin one personal or side-project repo. - Work through Steve Klabnik's Jujutsu tutorial; it explains the mental model better than the reference docs.
- Use jj for one real stack of PRs. Fall back to
gitwhenever you get stuck, in the same directory. - Decide after two weeks. If it does not stick, delete
.jjand nothing else changes.
Git won the version control war by being fast, distributed and good enough. Jujutsu does not try to replace that foundation. It keeps Git's storage and network model and replaces the part we interact with every day. For engineers who live in rebase and review cycles, that is the part that matters.
Sources and further reading:
- jj on GitHubjj-vcs/jj
- jj documentationdocs.jj-vcs.dev
- Steve Klabnik's Jujutsu tutorialsteveklabnik.github.io
- Chris Krycho on megamerges and jj absorbv5.chriskrycho.com
- LWN: Evolving Git for the next decadelwn.net
- 15 Years of Git, Then Jujutsulevelup.gitconnected.com
- I found Jujutsu before the Tech Radar didlevelup.gitconnected.com