name: jujutsu
description: >
Always activate FIRST on any git/VCS operations (commit, status, branch, push, etc.),
especially when HEAD is detached. If .jj/ exists -> this is a Jujutsu (jj) repo,
raw git commands can corrupt data. Essential git safety instructions inside.
allowed-tools: Bash(jj *)
Jujutsu (jj) version control system
Jujutsu is a Git-compatible VCS with mutable commits and automatic rebasing.
Tested with jj v0.46.0. Commands may differ in other versions.
Automated and agent environments
When running as an agent:
- Pass
--no-pagerto every command that produces output. A pager such aslesswaits for a keypress and hangs the run.JJ_PAGERoverridesui.pagerwhen you need a jj-specific setting.
jj --no-pager log
jj --no-pager diff
jj --no-pager show <id>
- Pass messages inline with
-m. Commands that open an editor fail without a terminal.
jj desc -m "message"
jj squash -m "message"
Confirm mutations with
jj st. Aftersquash,abandon,rebase, orrestore, check that the operation did what you expected.Avoid the interactive commands. They open a TUI or an editor and hang:
jj arrange,jj split,jj resolve,jj diffedit,jj file edit,jj squash -i, andjj absorb -i.Use
--no-integrate-operationto run a command without touching the repo state or the working copy, which helps when a background tool might snapshot at the same time.
Core concepts
The working copy is a commit
Your working directory is always a commit, referenced as @. Every jj command
snapshots your changes automatically. There is no staging area, and no need to
run jj commit.
Commits are mutable
Unlike git, jj commits can be rewritten after you create them. You can change the description, squash changes, rebase, and absorb without making new commits. See “Essential workflow” for the working pattern.
Change IDs vs commit IDs
- Change ID: a stable identifier such as
tqpwlqmpthat survives a rewrite. Prefer it when referencing commits. - Commit ID: a content hash such as
3ccf7581that changes with the content.
A change ID can name several hidden or divergent commits at once. Add a numeric
suffix to pick one: xyz/0 is the newest version and xyz/1 the one before it.
So jj restore --from xyz/1 --to xyz puts xyz back to its previous contents.
Revsets
jj selects commits with a revset language. Common revsets:
@, the working copy commit@-, the parent of the working copy::@, all ancestors of@@::, all descendants of@trunk()..@, the commits between trunk and@bookmarks(), every commit with a bookmark
Useful functions added in recent versions: divergent(), merge_point(),
forks(), remote_tags(), diff_lines(), diff_lines_added(),
diff_lines_removed(), and builtin_log() (the default jj log revset).
Pass revsets with -r: jj log -r 'trunk()..@'.
String patterns in revsets default to glob matching, so use the substring: or
exact: prefix for other kinds. Git-style symbols such as refs/heads/main no
longer resolve to revisions; use the bookmark or tag name.
Essential workflow
Starting work: describe first, then code
Write the commit message before the code.
Check that you’re on a blank revision with jj st. If not, start one:
jj new
Then describe the work and make the changes:
jj desc -m "Add user authentication to login endpoint"
# ... edit files ...
jj st
The edits become part of that commit on their own.
Creating atomic commits
Each commit should be one logical change. Message examples:
Add validation to user input forms
Fix null pointer in payment processor
Remove deprecated API endpoints
Viewing history
jj --no-pager log
jj --no-pager log -p
jj --no-pager show <change-id>
jj --no-pager diff --git
jj show takes several revisions at once, and --reversed shows a commit
against its parent.
About the jj diff format: the default output is a side-by-side line-number
layout (26 26:) that looks nothing like git’s +/- prefixes. It is
correct, not stale or corrupt. Use jj diff --git for a standard unified diff.
Moving between commits
jj new
jj new -m "Commit message"
jj edit <change-id>
jj prev -e
jj next -e
Refining commits
Squashing changes
jj squash
jj squash -i opens an interactive UI and hangs in an agent environment.
Splitting commits
jj split is interactive and hangs in an agent environment. To divide a commit,
use jj restore to move changes out and create the separate commits by hand.
Absorbing changes
jj absorb
Distributes working-copy changes to the commits that last touched those lines.
jj absorb -i and --tool are interactive; skip them in agent runs.
Abandoning commits
jj abandon <change-id>
Descendants are rebased onto the abandoned commit’s parent.
Undoing operations
jj undo
jj redo
These reverse or reapply the last operation, which recovers from a mistaken
abandon, squash, or rebase. They refuse to touch an operation from another
workspace unless you pass --allow-cross-workspace. To revert one specific
operation, use jj op revert; the old jj op undo was removed.
Rebasing commits
jj rebase -d <destination>
jj rebase -r <change-id> -d <destination>
jj rebase -s <change-id> -d <destination>
jj rebase -d main
Restoring files
jj restore
jj restore path/to/file.txt
jj restore --from <change-id> path/to/file.txt
Working with bookmarks
Bookmarks are jj’s branches.
jj bookmark create my-feature -r @
jj bookmark move my-feature --to <change-id>
jj --no-pager bookmark list
jj bookmark delete my-feature
Bookmarks don’t advance when you add commits, so move them by hand before
pushing. jj bookmark advance [names] moves bookmarks forward to a target in
one step, with --to defaulting to @.
jj bookmark track and untrack take --remote. Without it, every remote
bookmark with a matching name is tracked or untracked.
Working with tags
Tags use the same tracking model as bookmarks: fetched tags are tracked by default, and tracked tags are pushed by default.
jj --no-pager tag list
jj tag track <name>
jj tag untrack <name>
jj tag list supports --sort and can be filtered by revset.
Workspaces
A workspace is a working copy plus its repo. One repo can hold several
workspaces, each with its own working directory and @, all sharing the same
commits, operations, and bookmarks. It is jj’s version of git worktree, handy
for running a long build or test while editing elsewhere. Most tasks don’t need
one; see the
official docs for
anything beyond the basics.
Common commands
jj workspace add ../my-tests
jj workspace add --name tests -r <change-id> ../my-tests
jj --no-pager workspace list
jj workspace root [--name <ws>]
jj workspace rename <new-name>
jj workspace forget [<ws>]
jj workspace remove [<ws>]
forget leaves the directory on disk; remove deletes it too, after
snapshotting the working copy into a commit. add links by relative path, so a
workspace survives being moved together with its parent or run inside a
container. list shows each workspace root by default and no longer hides
unreachable paths.
In jj log, each workspace’s @ appears as <workspace-name>@.
Colocation
jj workspace add can create a Git worktree alongside the workspace with
--colocate/--no-colocate. jj git colocation status, enable, and
disable report or toggle this, and they work on child workspaces.
Key semantics
- Isolation by default. A new workspace gets its own fresh empty commit.
Workspaces don’t start out sharing
@, and files are never live-mirrored between them. - Propagation at command boundaries. Each command snapshots its own files and reads the op log, so it sees commits and bookmarks from other workspaces. There is no filesystem watcher.
- Stale working copy. If another workspace rewrites this workspace’s
@(viasquash,rebase, orabandon), jj refuses commands here until you runjj workspace update-stale. The same recovery applies after an interrupted command. - Shared
@is sharp-edged.jj edit <id>lets two workspaces point at the same change without warning. When one mutates it, the other goes stale; if the stale one had unsnapshotted edits,update-stalekeeps them as a divergent commit (same change ID, shown asxyz??injj log). Resolve the divergence withjj converge, or select one version with thexyz/0,xyz/1syntax. Avoid sharing@unless both workspaces are read-only.
Agent guidance
- Pass
--no-pagertojj workspace list. - Don’t
jj edita change that another workspace already has as@. That is the usual cause of accidental divergence. - Don’t
rm -rfa workspace directory without also runningjj workspace forget <name>.
Git integration
Working with existing Git repos
jj git clone <url>
jj git init --colocate
jj git init --colocate refuses to run inside a Git worktree and explains the
alternatives.
Fetching remote changes
jj git fetch
jj git fetch --remote <remote-name>
jj git fetch -b <branch-name>
jj git fetch reports abandoned commits and forwards git’s stderr, so
authentication prompts still reach you. It rebases local descendants onto
rewritten parents when the remote preserves change IDs. Tags arrive as
<name>@<remote> and are tracked by local tags of the same name. After
fetching, rebase your work: jj rebase -d main.
Switching between jj and git (colocated repos only)
This applies only to colocated repos, where .jj/ and .git/ both exist. In a
non-colocated repo, do not run git commands; they corrupt jj state.
Switch to git for a merge workflow:
jj st
git checkout <branch-name>
Switch back:
jj edit <change-id>
Git may complain about uncommitted changes when jj’s working copy differs from the git HEAD, so commit your jj work first. jj picks up git’s changes on the next command.
Pushing changes
jj git push -b <bookmark-name>
jj git push -b main
Before pushing, confirm the bookmark points at the right commit (bookmarks don’t auto-advance), the commits are refined and atomic, and the user asked for the push.
jj bookmark move my-feature --to @
jj git push -b my-feature
If no bookmark exists, create one first:
jj bookmark create my-feature
jj git push -b my-feature
jj git push can push to several remotes at once with repeated --remote,
allow commits that contain conflicts with --allow-conflicts, forward push
options with -o, and auto-track a bookmark given to --bookmark. The default
push targets come from revsets.git-push.
Handling conflicts
jj lets you commit conflicts and resolve them later.
jj st
Resolve conflicts by editing the files to remove the markers, then run jj st.
Don’t use jj resolve; it is interactive.
Running commands over revisions
jj run runs a command over a set of revisions, each in an isolated working
copy, then amends each revision with the result:
jj run -- cargo fix
jj run -j 4 -- cargo test
jj run --ignore-changes -- cargo clippy
It processes revisions oldest to newest by default. --passthrough connects the
subprocess to the terminal instead of capturing output, --ignore-changes runs
read-only without rewriting commits, and --ignore-errors continues after a
failing command.
Resolving divergence
jj converge combines the divergent revisions of a change into one
replacement, rebasing descendants and moving bookmarks onto it. It applies
heuristics and prompts when they don’t agree. Pass --no-interactive to print a
warning and exit instead of prompting, which is what an agent should do.
Inspecting files and other utilities
jj file search -p <pattern> # grep-like; prints each match with its path
jj file search -p <pattern> -n # add line numbers; --name-only lists files
jj file delete -r <rev> <path> # delete files in any revision
jj file show -r <rev> <path> # print a file from a revision
jj file annotate <path> # blame
jj util snapshot # snapshot the working copy
jj util diff <a> <b> # compare two files on disk
jj util backend name # show the commit backend
jj util gc # prune unreachable data in the store
jj config gc # drop config for deleted repos
jj bisect run --range <revs> <cmd> # find the first bad revision
jj file search matches by regex unless the pattern sets another kind, and it
requires -p. jj file edit edits a file in a revision but opens your editor,
so it is not for agent runs. jj bisect run checks its endpoints first; pass
--trust-endpoints to skip the checks.
Preserving commit quality
Because commits are mutable, refine them before you call the work done.
- Review:
jj --no-pager show @orjj --no-pager diff --git. - Keep one logical change per commit.
- Write the message as an imperative phrase in sentence case with no full stop: “Add login endpoint”, “Fix null pointer in payment processor”.
- Move unrelated changes out with
jj restore, then make separate commits. - Put changes in the right commit with
jj squashorjj absorb.
Quick reference
| Action | Command |
|---|---|
| Describe commit | jj desc -m "message" |
| View status | jj st |
| View log | jj --no-pager log |
| View diff | jj --no-pager diff --git |
| New commit | jj new -m "message" |
| Edit commit | jj edit <id> |
| Squash to parent | jj squash |
| Auto-distribute | jj absorb |
| Rebase | jj rebase -d <destination> |
| Abandon commit | jj abandon <id> |
| Undo last operation | jj undo |
| Restore files | jj restore [paths] |
| Search files | jj file search -p <pattern> |
| Run over revisions | jj run -- <cmd> |
| Resolve divergence | jj converge --no-interactive |
| Create bookmark | jj bookmark create <name> |
| Advance bookmark | jj bookmark advance <name> |
| Track tag | jj tag track <name> |
| Fetch remote | jj git fetch |
| Push bookmark | jj git push -b <name> |
| Add workspace | jj workspace add <path> |
| List workspaces | jj --no-pager workspace list |
| Remove workspace | jj workspace remove [name] |
| Forget workspace | jj workspace forget [name] |
| Fix stale working copy | jj workspace update-stale |
Best practices summary
- Describe first: set the message before coding.
- One change per commit.
- Use change IDs; they survive rewrites.
- Refine commits with
squashandabsorb. - No staging area, no stashing.