You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Many commands (squash, restore, diff, split, log, …) accept trailing
file paths to scope their effect to those files. Note jj squash --from <rev>
moves all of a revision's content unless you append paths — append them to
leave the rest behind.
Most Used
git
jj
git status
jj st
git diff HEAD
jj show
git diff <rev>^ <rev>
jj diff -r <rev>
git diff --from A --to B
jj diff --from A --to B
git show <rev>
jj show <rev>
git log --oneline --graph
jj log -r ::@
git log --all
jj log -r 'all()' --no-graph
git log -G <pattern>
jj log -r 'diff_lines(regex:"<pattern>")'
git blame <file>
jj file annotate <file>
git ls-files
jj file list
Committing & Describing
Action
Command
Commit all changes
jj commit -m "<message>"
Squash working copy → parent
jj squash
Squash working copy →
jj squash --into <rev>
Squash any →
jj squash --from <revA> --into <revB>
Squash specific files only
jj squash <file>
Edit working copy message
jj describe -m "<message>"
Edit parent's message
jj describe -m "<message>" @-
Edit any message
jj describe -m "<message>" <rev>
Branching & Navigation
git
jj
git switch -c topic main
jj new main
git checkout <rev>
jj new <rev>
git merge A
jj new @ A
git stash
jj new @-
git stash pop
jj squash --from <stashed-change-id>
Undo & Recovery
Action
Command
Hard reset working copy
jj abandon
Restore a file
jj restore <file>
Restore file from revision
jj restore --from <rev> <file>
Revert a revision
jj revert -r <rev> -B @
Cherry-pick
jj duplicate <rev>
Undo last operation
jj undo
View operation history
jj op log
Restore repo to operation X
jj op restore X
Rewriting History
(Squash variants are listed under "Committing & Describing" above.)
Unlike Git, jj stores conflicts as logical representations inside commits —
not as marker text in files. This means:
Rebasing a conflicted commit doesn't produce nested markers.
Conflicted commits can be further rebased, merged, or backed out.
No --continue workflow. You never get stuck mid-operation.
Auto-rebase works through conflicts (descendants rewrite automatically).
Resolution Workflow
Use this workflow for rebase, merge, and stacked-commit conflicts:
Find the earliest conflicted revision with an explicit revset:
jj log -r 'unmerged()' -n 20.
Create a working copy on that revision:
jj new <conflicted-rev>.
Inspect conflicted files with jj status; edit the materialized conflict
markers to the intended final content.
Confirm resolution with jj status.
Squash the working-copy resolution into the conflicted revision:
jj squash when the conflicted revision is @-, otherwise
jj squash --into <conflicted-rev>.
Inspect the rewritten stack explicitly:
jj log -r '<conflicted-rev>::' -n 30.
Descendants auto-rebase and may resolve automatically or expose additional
conflicts; resolve the nearest remaining child the same way.
Do not use jj resolve -r <ancestor> as the default agent workflow. It rewrites
an ancestor immediately and rebases its descendants in the same operation.
The built-in :ours and :theirs tools are appropriate only when explicitly
choosing an entire side.
There is no --continue, --abort, or special mode.
The default conflict text is not ordinary Git-style “take left/right” text:
one side may be a full snapshot and another a diff from the merge base. Read
the labels and apply each diff to the snapshot. Use
ui.conflict-marker-style = "snapshot" when full content for every side is
easier to review.
Understanding Conflict Markers
Default style: "diff"
The default marker style shows one side as a snapshot (full content) and the
other as a diff to apply to it:
<<<<<<< conflict 1 of 1
%%%%%%% diff from: vpxusssl "merge base"
\\\\\\\ to: rtsqusxu "commit A"
apple
-grape
+grapefruit
orange
+++++++ ysrnknol "commit B"
APPLE
GRAPE
ORANGE
>>>>>>> conflict 1 of 1 ends
Marker
Meaning
<<<<<<<
Start of conflict
>>>>>>>
End of conflict
+++++++
Start of a snapshot (full content)
%%%%%%%
Start of a diff to apply
\\\\\\\
Continuation of diff label
How to resolve: Apply the diff (grape → grapefruit) to the snapshot
(uppercase side). Result: APPLE / GRAPEFRUIT / ORANGE.
Alternative style: "snapshot"
Shows full content of every side. Set ui.conflict-marker-style = "snapshot":
<<<<<<< conflict 1 of 1
+++++++ rtsqusxu "commit A"
apple
grapefruit
orange
------- vpxusssl "merge base"
apple
grape
orange
+++++++ ysrnknol "commit B"
APPLE
GRAPE
ORANGE
>>>>>>> conflict 1 of 1 ends
Multi-sided Conflicts
When merging 3+ commits, jj produces one snapshot section and multiple diff
sections. Resolve by applying each diff to the snapshot one at a time.
Long Conflict Markers
If file contents contain lines that look like markers (e.g., =======), jj
uses longer markers (15+ characters) to avoid ambiguity:
<<<<<<<<<<<<<<< conflict 1 of 1
%%%%%%%%%%%%%%% diff from: wqvuxsty "merge base"
\\\\\\\\\\\\\\\ to: kwntsput "commit A"
-Heading
+HEADING
=======
+++++++++++++++ mpnwrytz "commit B"
New Heading
===========
>>>>>>>>>>>>>>> conflict 1 of 1 ends
Important Considerations
Don't panic about conflicts. They're normal commit state in jj — not an
error condition. You can keep working, create new commits on top, and resolve
later.
Do not use jj resolve as the default agent workflow. Resolve from a
child working copy as described above; use jj resolve --tool :ours or
:theirs only when explicitly choosing an entire side.
Undo if needed:jj undo reverses the last operation, allowing you to try
again if an undesired state results.
Check descendants: After resolving, descendants auto-rebase. Run
jj log to verify no remaining conflicts propagated. If there are conflicts,
continue at the nearest child.
Use jj (Jujutsu) instead of git for version control. Always activate immediately when the working directory contains a .jj/ directory. Overrides built-in git commit and PR workflows with jj equivalents.
user-invocable
true
allowed-tools
Bash(jj *)
Bash(gh pr *)
Bash(gh api *)
jj (Jujutsu) Version Control
jj version 2>/dev/null || echo "WARNING: jj not found on PATH"
jj workspace root 2>/dev/null || echo "WARNING: not inside a jj repository"
Use jj — never git — for all version control here. Learn any command with
jj <cmd> --help. Full git→jj table: commands.md. Conflict
guide: conflicts.md.
Key Differences from Git
No staging area. jj auto-snapshots the working copy on every command. No
git add; files are tracked by default.
Change IDs. Every revision has a stable Change ID (e.g. kpqxywon) that
survives rebases. Prefer Change IDs over commit hashes.
Bookmarks, not branches.jj bookmark replaces git branch. Detached
HEAD is the natural state; there is no "current branch."
Mutable commits.jj describe, squash, split, diffedit rewrite
history without ceremony. No git rebase -i.
Conflicts are first-class. Stored as logical state in commits, not
markers. Operations never stop mid-way; resolve at leisure. See conflicts.md.
Op log & undo.jj op log shows every operation; jj undo reverses the
last (repeatable). Safer than git reflog.
Revset Quick Reference
Revset
Meaning
@ / @- / @--
Working copy / parent / grandparent
::@
All ancestors of working copy
A..B
Ancestors of B that are not A or its ancestors
A::B
Revisions connecting A and B
bookmarks()
All bookmarked revisions
trunk()
Trunk/main bookmark (defined in repo config)
mine()
Revisions you authored
description("*<pat>*")
Revisions whose description matches
diff_lines("*<pat>*")
Revisions whose diff matches
Discover User Config First
The user's jj config can change command meaning, revsets, and output. Check
before relying on defaults:
jj config list --user # aliases, revset-aliases, templates, ui, git settings
jj config list --repo # trunk() definition, remote tracking
Watch for: custom trunk() and revset-aliases (don't guess unfamiliar revset
names); template-aliases/templates (alter log output, push bookmark names,
default messages); git.private-commits / git.sign-on-push (may block or
require signing on push); revsets.bookmark-advance-to; ui.default-command
(bare jj may not be log).
Colocated Repos (.git/ + .jj/)
Both dirs present = colocated. Still use jj only. jj auto-syncs to git refs
(jj git import/export) on most operations. Interleaving raw git commands
can cause bookmark conflicts.
Start exactly one fresh working-copy change before editing
Before making any code changes, ensure that @ is the one fresh, empty
working-copy change for the work. This is required even for small or "quick"
edits, but it is a one-time setup step:
If @ is already empty, undescribed, and at the intended place in the graph,
use it directly. Do not run jj new.
Otherwise, run exactly one jj new <base> -m "<intent>" before the first edit.
Pick <base> deliberately (usually @ to stack on current work, or
trunk() for independent work) and inspect the graph explicitly, for
example jj log -r '::@' -n 20; do not rely on bare jj log.
jj commit already finalizes the current change and creates the next empty
@. After jj commit, edit that new @ directly or describe it with
jj describe -m "<intent>"; do not run jj new @ afterward.
Never create an empty child from an empty @. There must not be a chain of
empty revisions interleaved with topical commits. jj new and the automatic
empty change created by jj commit are alternatives, not steps to perform
together.
Use jj new to start a fresh change, notjj edit (which moves the working
copy into an existing commit and edits it in place). Do not edit directly
on top of pre-existing uncommitted changes or on a commit the user is actively
working in; carve out your own change. Do not auto-squash or auto-commit
finished work into the user's commits unless asked. The dedicated workflows
below (e.g. megamerge scratch space and mechanical-transform splits) define
their own commit setup.
Working rhythm (commit per topical change; leave the rest for review)
Happy with a change? jj commit it — finalizes the current commit and
drops you on the one fresh empty working copy for the next topical
change. Continue from that @; do not add another empty change with
jj new. Describe it with jj describe -m "<intent>" when needed. One
commit per coherent unit of work, not one sprawling working copy.
Do not auto-squash at task end. Leave the working copy and descendants
intact so the user reviews the stack as discrete commits. Squashing/reordering
is theirs to do — free later (see the jj squash gotcha below).
On resume (new task or context reset), run jj diff early — with
jj status and an explicit graph query such as jj log -r '::@' -n 20 — to
see what's already in the working copy before touching anything. Bare jj log
is subject to user defaults; don't assume it shows the relevant stack.
When asked to commit, use jj — never git. There is NO staging step.
Understand current state (run in parallel): jj status, jj diff,
jj log -r '::@' -n 10 (recent descriptions for style). Use an explicit
revset; bare jj log follows user defaults.
Draft a concise (1–2 sentence) message focused on the "why." Don't commit
files that likely contain secrets.
jj commit -m "<message>" — NEVER append a Co-Authored-By trailer.
jj st to verify (a new empty working copy is expected and normal).
Do NOT use git add/git commit. Do NOT push unless asked. If something goes
wrong, jj undo reverses the last operation.
Run in parallel: jj git fetch, jj st, jj diff,
jj log -r 'main..@' -n 100 (the full graph that will land in the PR).
Always pass the revset and limit explicitly; do not rely on bare jj log.
Analyze all revisions in the PR (not just the latest); draft title +
summary.
Commit any uncommitted changes per the workflow above.
Push, auto-creating a tracked bookmark: jj git push --change @- (@- is
the just-committed revision; rerun to move the bookmark forward). For a
chosen name use --named <name>=<rev>.
A merge commit unifies its parents' contents. When the user keeps an
integration commit (often bookmarked megamerge) that merges several feature
branches, jj new megamerge starts a working copy whose content is the union
of all those branches — including changes you just committed on one of the
parent branches. The merged files appear with no rebase.
Don't rebase to "bring files together." If the file you need lives on
branch A and the toolchain (Makefile, deps) lives on branch B, and both are
parents of megamerge, jj new megamerge gives you both at once. Rebasing A
onto B is the wrong move and rewrites history the user may not want.
A just-made working-copy edit on a parent branch is auto-snapshotted into a
commit, so it is already part of what jj new megamerge sees — no need to
commit-then-rebase first.
This new commit is throwaway scratch space (build/validate here). The real
work stays on its own branch; megamerge is only an integration view.
If the user is "on a branch rather than atop the megamerge" and files seem
missing, the fix is jj new megamerge, not reconstructing the files.
Splitting a Mechanical Transform out of a Mixed Commit
jj split <files> cuts at file boundaries; jj split -i --tool <ui> needs an
interactive diff editor an agent cannot drive. When the change to isolate is a
pure textual transform (e.g. go → $(GO), a rename, a reformat) tangled
with logic in the same file, reproduce it deterministically instead of picking
hunks:
Build a scoped sed/perl that reproduces only the transform. Restrict it
(e.g. recipe lines, specific assignments) and exclude lines the logic commit
also rewrites, so the two pieces never collide on the same line.
Dry-run first: apply it to the parent's file in a temp copy and diff
against the original tip's file. Every residual difference must be a
substantive change — proof the transform is neither over- nor under-reaching.
jj new <target>- to start a commit on the target's parent (working copy now
holds parent content), apply the transform in place, describe it.
jj rebase -r <target> -d @ to stack the original commit on top. Expect no
conflicts (step 1 kept them on disjoint lines).
Verify end state: jj file show -r <target> <file> must be byte-identical
to the original tip — the split must not alter the result. Confirm with
diff - <saved-original>.
This is fully automatable and self-verifying, unlike any --tool diff editor.
Gotchas Beyond the Basics
Conflicts are not a failure state — leaving them is often fine. A conflict
is recorded in the commit as logical state (see conflicts.md), travels with
the commit, and is resolvable anytime. No need to resolve the moment one
appears; keep working, return later (or leave it if it's in the user's
territory).
Resolve conflicts in a child working copy. Find the earliest conflicted
revision with an explicit query such as jj log -r 'unmerged()' -n 20, run
jj new <rev>, edit its materialized conflict markers, verify jj status,
then jj squash or jj squash --into <rev>. Descendants auto-rebase and may
resolve or expose later conflicts; inspect them with an explicit query such as
jj log -r '<rev>::' -n 30. Avoid resolving an ancestor directly with
jj resolve -r, which rewrites the stack immediately.
jj squash --into <earlier> may create descendant conflicts — expected, not
a block. Auto-rebase rewrites descendants and may surface a conflict there;
the squash still succeeds and the op completes. Don't avoid jj squash
reflexively — let it happen, resolve later if/when it matters.
Avoid the interactive-editor hang on squash.jj squash opens $EDITOR
to combine descriptions when it can't decide non-interactively, which hangs an
agent. Steer clear:
Both source and destination have descriptions → pass a message:
jj squash --from <rev> --into <rev> -m "...".
Working copy into a named rev → --use-destination-message (keep target's)
or --use-source-message also stays non-interactive.
Bookmark-list templates need the self. prefix on methods. In
jj bookmark list -T '...', qualify method calls, e.g.
self.normal_target().commit_id(). Bare keywords work only for top-level
names like name; a bare normal_target() won't resolve.