Why jj clicked for me
The thing I like most about jj is not a command.
It lowers the cost of mistakes.
I can try something before I know if it is the right shape. I can share work before it is perfect. I can split it later. I can throw it away. I can let an agent try a fix, review the diff, and undo it if I do not like it.
That changes my behavior. I stop trying to design the perfect path up front. I start moving, looking, reshaping, and asking for feedback sooner.
The first idea that makes this work is simple: my working copy is a change. Its name is @.
I can diff @. I can commit it. I can split it. I can squash it into another change. I can abandon it. I can rebase it. Even unfinished work has an address in the graph.
The second idea is guardrails.
I can be messy locally, but jj still protects the shared parts:
- trunk, tags, and untracked remote bookmarks are protected from accidental rewrites
- my draft
push-*review branches can still move, but pushes go through safety checks
That balance is why I like it. I can move fast locally, and jj still makes me check the remote before I push.
This fits stacked PRs very well. I can split work into small reviewable commits, push them early, keep moving while review happens, and avoid turning every feature into one giant pull request.
When the shape is wrong, I reshape it. When the experiment is bad, I undo it. When the piece is ready, I push it.
That is why commands like duplicate, squash, restore, and undo matter so much to me. They make it easier to be wrong for a while. That is useful when the work is unclear, when review starts early, or when I need to learn by trying.
There is one small bit of syntax worth learning early.
@ is the current working-copy change:
jj diff -r @
That means “show me the diff for my current working-copy change”.
@- is the parent of @:
jj diff -r @-
That means “show me the diff for the parent of @”.
The -r flag means “revision”. In jj, the value passed to -r can be a revset: a small expression that selects changes.
For example:
jj log -r 'main..@'
That means “show me the changes reachable from @ that are not in main”.
After this command:
jj commit -m "feat: add token validation"
@- is now usually the change I just committed, because it is the parent of the new empty @.
That is why I run this all the time:
jj diff -r @-
It shows the last completed change. That is exactly what I want after committing, resolving conflicts, or letting an agent modify code.
For a fuller introduction, Stavros’ tutorial, Jujutsu for everyone, and Steve Klabnik’s tutorial are good companion explanations. The official Jujutsu docs are the place to check exact command behavior when this guide is too informal.