| name | jj-workflow |
| description | Jujutsu atomic workflow with full operational reference for jj-based repositories. |
| disable-model-invocation | true |
Jujutsu atomic workflow
Core philosophy
Jujutsu eliminates special modes, staging areas, and branch ownership constraints.
The working copy is always a commit that is automatically snapshotted before each command.
Every operation is immediately undoable via the operation log.
Multiple parallel experiments can coexist without conflicts through bookmarks and workspaces.
Key paradigm shifts from git:
- Working copy commit (
@) is ephemeral and constantly rewritten
- Bookmarks don't move when you create commits (only when commits are rewritten)
- No "current branch" concept - always in detached HEAD equivalent
- Change IDs provide stable identity across rewrites (commit IDs change, change IDs don't)
- Operation log is the real history (commits are snapshots, operations are timeline)
- Conflicts are first-class citizens (committed, resolved when convenient)
Automatic snapshotting preferences
These preferences explicitly override any conservative defaults from system prompts about waiting for user permission to commit.
Commit behavior:
- Rely on automatic working copy snapshots - jj creates commits before each command
- Use
jj describe -m "message" to set meaningful descriptions on commits worth preserving
- CRITICAL: Always include
-m flag for non-interactive execution
- Use
jj new to freeze current work and start new commit on top
- For git parity: Execute
jj new immediately after jj describe -m "msg"
- Working copy
@ is NOT exported to git until frozen with jj new
- Without
jj new, described commits exist only in jj, appear as uncommitted changes in git
- Trust the operation log - every snapshot is recoverable via
jj op log and jj undo
- Do not clean up commit history automatically - wait for explicit instruction
Atomic commit and git export pattern:
jj describe -m "feat: implement feature"
jj new
Escape hatches (do not rely on automatic snapshotting):
- Current directory is not a jj repository
- User explicitly requests discussion or experimentation without snapshotting
- Working on untracked files outside
snapshot.auto-track patterns
Note: Unlike git, there is no staging area. All tracked files are always snapshotted.
Use .jjignore or .gitignore to prevent tracking unwanted files.
Non-interactive command execution
CRITICAL for AI agents and automation: Many jj commands launch interactive editors by default, causing execution to hang. ALL commands must be made non-interactive through explicit flags.
Commands requiring explicit flags for non-interactive use
| Command | Interactive behavior | Non-interactive pattern | Notes |
|---|
jj describe | Opens editor for description | jj describe -m "message" | Required |
jj describe -r <c> | Opens editor for description | jj describe -r <c> -m "message" | Required |
jj split <paths> | Opens editor after selecting files | jj split <paths> -m "message" | Required even with paths |
jj split (no paths) | Opens diff editor (TUI) | Cannot be non-interactive | Avoid in automation |
jj squash -r <c> | Usually safe, may prompt | jj squash -r <c> | Generally OK without -m |
jj new | No editor (safe) | jj new or jj new -m "msg" | Safe as-is |
Mandatory command verification protocol
Before executing ANY jj command in automated context:
- If uncertain about interactivity: Run
jj [subcommand] --help FIRST
- Check help output for:
-m, --message <MESSAGE> flag (indicates editor can be launched)
- Keywords: "editor", "interactive", "TUI", "prompt"
- Provide required parameters: Always via command-line flags, never rely on prompts
Common gotchas and solutions
jj split requires -m even with explicit paths:
jj split file.txt
jj split file.txt -m "refactor: extract file.txt changes"
jj split file1.txt file2.txt
jj split file1.txt file2.txt -m "feat: add new files"
Parameterless jj split cannot be made non-interactive:
jj split
jj split <specific-paths> -m "description"
jj describe without -m always opens editor:
jj describe
jj describe -m "feat: implement feature"
jj describe -r <commit>
jj describe -r <commit> -m "updated description"
Git parity and the jj new requirement
Critical understanding: jj working copy @ exists only in .jj/ metadata until explicitly frozen.
The problem:
- Described commits in jj
@ are NOT automatically exported to git
- From git's perspective,
@ appears as uncommitted working directory changes
- This breaks git-based tooling, CI/CD, and collaboration workflows
The solution - atomic commit pattern with immediate git export:
jj describe -m "feat: implement feature X"
jj new
Without jj new:
jj describe -m "feat: done"
jj git push --bookmark main
With jj new:
jj describe -m "feat: done"
jj new
jj git push --bookmark main
When to use jj new:
- After every
jj describe -m "message" for atomic commits
- When preparing commits for git operations (push, PR creation, etc.)
- When you want to checkpoint work and start the next logical change
When jj new is optional:
- During rapid experimentation where git visibility doesn't matter
- When using jj-only features (workspaces, operation log recovery)
- Will eventually need it before any git interaction
Escape hatches for interactive operations
If interactive command unavoidable:
- Warn user that manual interaction required
- Provide exact command for user to run
- Document why automation cannot handle it
- Never execute commands with
-i or --interactive flags in automation
Foundation: Atomic commit workflow
Working copy commit behavior
The working copy is always the @ commit:
- All file changes automatically amend
@ without explicit commands
@ is rewritten in place as you work (new commit ID, same change ID)
- Use
jj describe -m "message" to add description when changes represent cohesive unit
- Required:
-m flag for non-interactive execution (never omit it)
- Use
jj new to freeze @ and create new empty @ on top
- Git export: Frozen commits become visible in git;
@ remains jj-only until frozen
- Atomic workflow:
jj describe -m "msg" → jj new → commit exported to git, ready for next change
- Use
jj commit to move @ changes into its parent (alternative to jj new)
Critical for git parity:
Without jj new, your described working copy commit exists only in jj's .jj/ directory.
From git's perspective, these changes remain uncommitted in the working directory.
Execute jj new after each jj describe -m "msg" to maintain git/jj synchronization.
Organizing changes:
- Let related changes accumulate in
@
- Use
jj split <paths> -m "message" when changes diverge into separate concerns
- Required:
-m flag even when providing paths (common mistake to omit)
- Use
jj squash to move changes between commits
- Use
jj absorb to automatically distribute fixes to appropriate ancestors
File state awareness:
- Run
jj status to see what's in current @
- Run
jj diff to review changes in @
- No staging area to check - working copy state is commit state
Bookmark management
Bookmarks are named pointers that don't move automatically with new commits.
Core behavior:
- Bookmarks stay on their target when you create new commits (unlike git branches)
- Bookmarks only move when commits are rewritten (rebase, squash, abandon)
- Update bookmarks explicitly:
jj bookmark set <name> -r <commit>
- Create bookmarks for important points:
jj bookmark create <name> -r <commit>
- Always work in "detached HEAD" state - this is normal in jj
Naming conventions:
- Main bookmarks:
main, beta, staging
- Feature work:
issue-N-descriptor (e.g., issue-42-add-auth)
- Experiments:
exp-N-description (e.g., exp-1-refactor-parser)
- Archives:
archive/old-bookmark-name
Integration with issue tracking:
- When work diverges from current bookmark's purpose, create new bookmark
- Example: working near
issue-42-auth but fixing unrelated bug → jj bookmark create issue-58-logging
Default bias: bookmarks are cheap, use them liberally to mark important commits.
Operation log and recovery
Every jj operation is atomic and recorded in the operation log.
Core commands:
jj undo - undo last operation (any operation, not just commits)
jj op log - view complete operation history
jj op restore <id> - restore repo to exact prior state
jj op show <id> - see what an operation changed
Recovery patterns:
- Mistake in last operation:
jj undo
- Mistake several operations ago:
jj op log, then jj op restore <id>
- Want to undo operation N but keep N+1:
jj op restore to N-1, manually redo N+1
- Concurrent operations created divergence: inspect with
jj log, resolve with jj bookmark set
Key insight: Operation log is your safety net, not backup branches.
Delete bookmarks freely - commits remain in operation log.
Conflict management
Conflicts are first-class citizens, committed and resolved when convenient.
Conflict workflow:
- Operations never fail due to conflicts - conflicts are committed with marker
- Continue working on other commits while conflicts exist
- View conflicted commits:
jj log -r 'conflict()'
- Resolve when ready:
jj new <conflicted-commit>, fix files, jj squash resolution back
- Or resolve in place:
jj edit <conflicted-commit>, fix files (automatically amends)
Conflict tools:
jj resolve - launch merge tool for each conflict
jj resolve --list - see all conflicts in current commit
- Edit conflict markers directly in files or use merge tools
Never blocked by conflicts - they're just another commit state.
Parallel experimentation with bookmarks
Start with bookmarks in single workspace. Graduate to separate workspaces only when needed.
Starting experiments from main
jj bookmark create exp-1-nix-flakes -r main
jj bookmark create exp-2-home-manager -r main
jj bookmark create exp-3-unified-config -r main
jj new exp-1-nix-flakes
jj describe -m "[exp-1] feat(nix): migrate to flakes - part 1"
jj new
jj describe -m "[exp-1] feat(nix): migrate to flakes - part 2"
jj new
jj new exp-2-home-manager
jj describe -m "[exp-2] feat(home): initial home-manager setup"
jj new
Viewing and comparing experiments
Query experiments using revsets:
jj log -r 'main.. & (exp-1-nix-flakes:: | exp-2-home-manager:: | exp-3-unified-config::)'
jj log -r 'main..exp-1-nix-flakes'
jj diff -r 'main..exp-1-nix-flakes'
jj log -r '(main..exp-1-nix-flakes) ~ (main..exp-2-home-manager)'
jj log -r '(main..exp-1-nix-flakes) & (main..exp-2-home-manager)'
jj log -r 'main..exp-1-nix-flakes' --no-graph --template 'commit_id ++ "\n"' | wc -l
Checkpoint and push experiments
jj bookmark set exp-1-nix-flakes -r @-
jj git push --bookmark exp-1-nix-flakes
Advantages of bookmark-only experiments
- Minimal overhead (no separate directories)
- Fast switching between experiments
- Operation log tracks everything
- No stale workspace issues
- Lower disk space usage
Disadvantages:
- Working tree changes when switching (like git checkout)
- Cannot run tests in parallel
- Cannot compare files side-by-side easily
Graduating to workspaces
Create separate workspaces when experiments need simultaneous file access.
When to create workspaces
Create workspace when you need:
- Long-running builds/tests in each experiment simultaneously
- Side-by-side file comparison in editors
- Different sparse patterns per experiment
- Avoid working-tree thrashing from frequent switches
Keep bookmark-only when:
- Quick exploration or prototyping
- Comparing implementations conceptually
- Don't need files from multiple experiments on disk
Creating workspaces for experiments
jj workspace add ../myproject-exp-1 -r exp-1-nix-flakes
cd ../myproject-exp-1
jj describe -m "[exp-1] feat(nix): add flake inputs"
jj new
nix build .# &
cd ../myproject
jj new exp-2-home-manager
jj describe -m "[exp-2] feat(home): configure programs"
Workspace-specific concepts
Working-copy commits:
- Each workspace has unique working-copy commit tracked in repository view
@ refers to current workspace's working-copy commit
exp1@ refers to workspace named "exp1" working-copy commit
jj log -r 'working_copies()' shows all workspace working-copy commits
Stale workspaces:
- Workspace becomes stale when its working-copy commit is rewritten from another workspace
- Example: rebase
exp1@ from primary workspace → exp1 workspace becomes stale
- Warning appears when running commands in stale workspace
- Fix with
jj workspace update-stale to update files to new commit
Cross-workspace operations:
- Can work near same bookmark from multiple workspaces (no exclusive ownership)
- Can read/query any commit from any workspace
- Rewriting another workspace's working-copy commit makes it stale
- Safe:
jj new exp1@ (create new commit on top)
- Risky:
jj edit exp1@ (edit directly, will cause staleness if files change)
Workspace lifecycle
jj workspace list
jj workspace add <path> -r <starting-commit>
jj workspace forget <workspace-name>
rm -rf <workspace-path>
myproject/
myproject-exp-1/
myproject-exp-2/
Experiment lifecycle management
Scale to arbitrary number of experiments with minimal complexity.
Naming conventions
Bookmarks:
exp-{number}-{short-description}
- Examples:
exp-1-refactor-parser, exp-2-add-async, exp-3-optimize-cache
Workspaces (when needed):
{repo}-exp-{number}
- Examples:
myproject-exp-1, myproject-exp-2
Commit descriptions:
- Active:
[exp-N] type: description
- Review:
[exp-N:review] type: description
- Archived:
[exp-N:archived] type: description
- Winner:
[exp-N:winner] type: description
Revset aliases for scaling
Add to ~/.jjconfig.toml:
[revset-aliases]
'exps()' = 'main.. & ~main'
'exp(x)' = 'main..x'
'exp_diff(x, y)' = '(main..x) ~ (main..y)'
'exp_shared(x, y)' = '(main..x) & (main..y)'
'wcs()' = 'working_copies()'
'exp_wcs()' = 'working_copies() & ~default@'
Usage:
jj log -r 'exps()'
jj log -r 'exp(exp1@)'
jj log -r 'exp_diff(exp1@, exp2@)'
jj log -r 'exp_shared(exp1@, exp2@)'
Experiment registry
Maintain docs/experiments.md in repository:
# Experiments Registry
## Active
### exp-1-refactor-parser
- Status: Active
- Bookmark: `exp-1-refactor-parser`
- Workspace: `myproject-exp-1`
- Goal: Rewrite parser for 10x performance
- Created: 2025-10-15
- PR: #123
### exp-2-add-async-support
- Status: Active
- Bookmark: `exp-2-add-async-support`
- Workspace: None (bookmark only)
- Goal: Add async/await throughout
- Created: 2025-10-14
- PR: None yet
## Review
### exp-3-optimize-cache
- Status: Ready for review
- Bookmark: `exp-3-optimize-cache`
- Workspace: `myproject-exp-3`
- Goal: LRU cache for 30% speedup
- Created: 2025-10-10
- PR: #120
## Archived
### exp-4-graphql-api
- Status: Archived (too complex)
- Bookmark: `archive/exp-4-graphql-api`
- Workspace: Removed
- Goal: GraphQL API layer
- Created: 2025-10-01
- Archived: 2025-10-12
- Reason: Scope too large, splitting into smaller experiments
## Integrated
### exp-5-docker-support
- Status: Merged to main
- Bookmark: Deleted
- Goal: Add Docker deployment
- Created: 2025-09-20
- Merged: 2025-10-01
PR: #105
State transitions and lifecycle
jj describe -r 'description(glob:"*[exp-N]*")' -m "[exp-N:review] ..."
jj git push --bookmark exp-N
jj rebase -s 'main..exp-N' -d main
jj bookmark set main -r exp-N
jj git push --bookmark main
jj bookmark rename exp-N archive/exp-N
jj bookmark delete archive/exp-N
jj log -r 'description(glob:"*[exp-*:review]*")'
jj log -r 'description(glob:"*[exp-*:archived]*")'
Periodic cleanup
jj bookmark list | grep '^exp-'
jj log -r 'bookmarks() & description(glob:"exp-*")' \
--template 'bookmarks ++ " " ++ committer.timestamp() ++ "\n"'
jj bookmark rename exp-old archive/exp-old
jj abandon 'empty() & exps()'
jj op log --limit 50
jj op restore <op-id>
Experiment dependencies
Stack experiments when one depends on another:
jj bookmark create exp-1 -r @
jj new exp-1
jj describe -m "[exp-2] feat: builds on exp-1"
jj bookmark create exp-2 -r @
jj rebase -r exp-1 -d main
Merge experiments when needing multiple:
jj new exp-1 exp-2
jj describe -m "[exp-3] feat: combines exp-1 and exp-2"
jj bookmark create exp-3 -r @
Cherry-pick specific commits:
jj new main
jj squash --from <specific-commit> --into @
jj describe -m "[exp-4] feat: uses technique from exp-1"
History refinement
Transform experimental development history into clean, reviewable commit sequence.
Principles:
- Operations execute immediately and atomically
- No special modes or interactive editors
- Operation log is safety net (not backup branches)
- Descendants auto-rebase when ancestors change
- Conflicts are committed, not blocking
Incremental cleanup workflow
jj log -r 'main..@'
jj squash -r 'description(glob:"fixup*") & main..@'
jj squash -r 'description(glob:"oops*") & main..@'
jj abandon 'empty() & main..@'
jj squash -r 'description(glob:"WIP:*") & main..@'
jj log -r 'main..@'
jj rebase -r <commit> -d <new-parent>
jj rebase -r <commit> -A <after>
jj rebase -r <commit> -B <before>
jj describe -r <commit> -m "proper conventional commit message"
jj log -r 'main..@'
jj diff -r 'main..@'
Each step executes immediately. Use jj undo to back out of any step.
Core operations
Reorder commits:
jj rebase -r <commit> -d <parent>
jj rebase -r <commit> -A <after-commit>
jj rebase -r <commit> -B <before-commit>
jj rebase -s <commit> -d <new-base>
Note on -A and -B: These flags automatically rebase all descendants of the moved commit onto it in a single atomic operation. No second rebase command needed to reconnect the chain.
Using change ID prefixes: For faster typing, use unambiguous prefixes of change IDs:
jj rebase -r tknpxpos -A ynrpuxsz
jj rebase -r tk -A y
Moving ranges vs single commits: Choose between -r and -s based on what you're moving:
jj rebase -r <commit> -A <after>
jj rebase -s <first-commit> -d <destination>
Use -r with -A/-B when moving one commit within a chain. Use -s when you want to move multiple commits together as a unit - the specified commit plus everything built on top of it will move to the new destination. This is the primary way to "slide" a range of commits through history.
Squash commits:
jj squash -r <commit>
jj squash -r <commit> -m "combined message"
jj squash --from <commit> --into <ancestor>
jj squash -i -r <commit>
jj squash -r 'description(glob:"WIP:*") & main..@'
Drop commits:
jj abandon <commit>
jj abandon 'description(glob:"tmp:*") & main..@'
jj abandon 'empty() & main..@'
jj abandon <start>::<end>
Reword descriptions:
jj describe -r <commit> -m "new description"
jj describe -r <commit>
jj describe -r 'description(glob:"WIP*") & main..@' -m "proper: description"
jj describe -r <commit> -m ""
Split commits:
jj split <paths> -m "description for selected changes"
jj split src/feature.rs -m "feat: implement feature X"
jj split file1.txt file2.txt -m "docs: add documentation"
jj split -r <commit> <paths> -m "refactor: extract changes"
jj split -r <commit>
jj split <paths>
jj split -i -r <commit>
jj split <paths> -m "description"
CRITICAL: jj split requires -m "message" flag when providing paths to avoid launching
editor for description. Without -m, the command succeeds in selecting files but then
blocks waiting for interactive description input. This is the most common mistake.
After splitting, remember to freeze commits for git export:
jj split file.txt -m "refactor: extract changes"
jj describe -m "refactor: remaining changes"
jj new
Edit commit content:
jj edit <commit>
jj new @-
jj diffedit -r <commit>
jj edit <commit>
jj commit <files>
jj new @-
Auto-distribute changes with absorb
Automatically move fixes to commits that last touched those lines:
jj absorb
Most powerful for fixing issues found during review.
Verification workflow
After cleanup:
jj log -r 'main..@'
jj diff -r 'main..@'
for commit in $(jj log -r 'main..@' --no-graph --template 'commit_id ++ "\n"'); do
jj new $commit --no-edit
nix build .# || echo "Build failed: $commit"
done
jj new @-
jj log -r 'conflict() & main..@'
jj op log --limit 20
Complete cleanup example
Starting state:
jj log -r 'main..@'
Goal: 2 clean commits (one per feature)
jj squash --from zzz999 --into abc123
jj abandon yyy888
jj squash -r xxx777
jj squash -r def456
jj squash -r jkl012
jj squash -r mno345
jj describe -r abc123 -m "feat: implement feature X with tests"
jj describe -r ghi789 -m "feat: implement feature Y with error handling"
jj log -r 'main..@'
jj new abc123 && nix build .# && jj new @-
jj new ghi789 && nix build .# && jj new @-
jj bookmark set feature-xy -r @
If any step fails, jj undo backs out immediately.
Advanced patterns
Integration strategies
Rebase winner onto main (preserves commit history):
jj rebase -s 'main..exp-1-winner' -d main
jj bookmark set main -r exp-1-winner
jj git push --bookmark main
Squash winner into main (clean single commit):
jj new main
jj squash --from 'main..exp-1-winner' --into @
jj describe -m "feat: feature from experiment 1
Squashed from exp-1-winner.
Includes: ..."
jj bookmark set main -r @
jj git push --bookmark main
Merge experiments (when both valuable):
jj new exp-1-winner exp-2-winner
jj describe -m "feat: merge experiments 1 and 2"
jj bookmark set main -r @
jj git push --bookmark main
Sub-experiments within experiments
jj bookmark create exp-1-parser -r main
jj new exp-1-parser
jj describe -m "[exp-1a] feat: regex parser"
jj bookmark create exp-1a-regex -r @
jj new exp-1-parser
jj describe -m "[exp-1b] feat: PEG parser"
jj bookmark create exp-1b-peg -r @
jj diff -r 'exp-1-parser..exp-1a-regex'
jj diff -r 'exp-1-parser..exp-1b-peg'
jj rebase -s exp-1b-peg -d main
jj bookmark set exp-1-parser -r exp-1b-peg
jj bookmark delete exp-1a-regex
Experimental feature flags
Merge experiment to main early behind feature flag:
jj new main
jj describe -m "[exp-4] feat: add experimental cache
Behind EXPERIMENTAL_CACHE feature flag."
jj bookmark set main -r @
jj git push --bookmark main
jj workspace add ../myproject-exp-4-cache -r @
Session workflow pattern
Effective jj session:
jj git fetch
jj log
jj new <base>
jj describe -m "message"
jj split
jj squash
jj undo
jj log -r 'main..@'
jj describe -r <commits>
jj bookmark set <name> -r @
jj git push --bookmark <name>
jj op log
jj op restore <id>
Git colocated mode
Jujutsu operates in colocated mode with existing git repositories, allowing seamless interoperation.
Initializing colocated repository
cd /path/to/git/repo
jj git init --colocate
ls -la
jj log
Alternative initialization:
jj git clone <url> <directory>
jj git init --git-repo=.
Colocated workflow
In colocated mode:
- All jj bookmark operations automatically sync to git branches
- Git commands work on same repository - changes appear in
jj log
jj git import imports git changes (automatic in colocated mode)
jj git export exports to git refs (automatic in colocated mode)
- Git-specific features (GitHub PRs, etc.) work with jj-managed bookmarks
Workflow:
- Prefer jj commands for all operations
- Git commands work but are less powerful
- After git operations, check
jj log to see imported changes
- Operation log tracks git operations as "import git refs"
Note on detached HEAD:
jj operates in "detached HEAD" mode (git terminology). This is normal and does not affect jj operations - jj git push --bookmark <name> works correctly without an attached HEAD. Only attach HEAD if switching to git-native workflows:
git checkout main
git switch main
jj operations may detach HEAD again. This is expected behavior.
Remote synchronization
jj git fetch --all-remotes
jj bookmark track <name>@<remote>
jj git push --bookmark <name>
jj git push --all
jj git push --tracked
Log visibility and remote bookmarks
After pushing commits to a remote, jj log hides them by default to focus on work-in-progress.
Symbols indicating commit state:
○ - Mutable commit (local, not pushed)
◆ - Immutable commit (exists on remote)
~ - History truncated (more commits exist but are hidden)
jj git push --bookmark main
jj log
jj log -r 'all()'
jj log -r '::@'
jj log -r 'main@origin::'
Customizing log visibility:
To always show full history, configure default revset in ~/.jjconfig.toml:
[revsets]
log = "@ | ancestors(bookmarks() | tags() | remote_bookmarks(), 2)"
Or create an alias for full history:
[aliases]
la = ["log", "-r", "all()"]
This default behavior keeps your log focused on uncommitted work while clearly marking the boundary between local changes and pushed commits.
Reverting to git-only operations
Since colocated mode maintains both .git/ and .jj/, you can revert to git-only operations at any time:
git status
git log
git checkout <branch>
rm -rf .jj/
Key insight: Colocated mode provides a safe, reversible migration path.
Your git repository is never at risk - jj adds capabilities without replacing git.
Reference
Essential revset patterns
Query language for selecting commits:
@
<workspace>@
<bookmark>
<bookmark>@<remote>
root()
A..B
A::B
A | B
A & B
~A
A ~ B
mine()
author("pattern")
description(glob:"pattern")
file("path")
~/path
empty()
conflict()
merge()
bookmarks()
working_copies()
main..@
mine() & ~bookmarks()
description(glob:"WIP*") & main..@
conflict() & main..@
Common command patterns
jj new <base>
jj new <parent1> <parent2>
jj describe -m "message"
jj commit
jj split
jj edit <commit>
jj squash
jj squash -r <commit>
jj squash --from <src> --into <dst>
jj squash -i
jj absorb
jj rebase -r <commit> -d <dest>
jj rebase -s <commit> -d <dest>
jj rebase -r <commit> -A <after>
jj rebase -r <commit> -B <before>
jj abandon <commit>
jj bookmark create <name>
jj bookmark create <name> -r <commit>
jj bookmark set <name> -r <commit>
jj bookmark rename <old> <new>
jj bookmark delete <name>
jj bookmark list
jj bookmark track <name>@<remote>
jj workspace add <path> -r <commit>
jj workspace list
jj workspace forget <name>
jj workspace update-stale
jj
jj -r <revset>
jj diff -r <commit>
jj show <commit>
jj evolog -r <commit>
jj op
jj op show <>
jj undo
jj op restore <>
jj git init --colocate
jj git <url>
jj git fetch
jj git push --bookmark <name>
jj git push --all
Session operation summary
After working session, provide summary:
jj op log --limit 20
jj log -r 'bookmarks()..@'
jj op log --limit N
jj log -r 'main..@'
Use explicit operation IDs from session start for precise ranges.
Key principles
- Non-interactive execution required: Always use
-m flag with commands that can prompt (jj describe, jj describe -r, jj split <paths>); verify unfamiliar commands with jj [subcommand] --help before execution
- Git parity via jj new: Working copy
@ is not exported to git until frozen; execute jj new after jj describe -m "msg" to make commits visible in git and maintain synchronization
- Command verification protocol: When uncertain if a command is non-interactive, run
--help first and check for -m, --message flag or interactive keywords
- Operation log is history (commits are snapshots, operations are timeline)
- Bookmarks don't move automatically (only on commit rewrites)
- Working copy commit
@ is ephemeral (constantly rewritten)
- Change IDs provide stability (commit IDs change, change IDs don't)
- No staging area (working copy state is commit state)
- Conflicts are first-class (committed, resolved when convenient)
- No special modes (all operations execute immediately)
- Start with bookmarks (graduate to workspaces when needed)
- Describe atomically (one logical change per description)
- Trust operation log (delete bookmarks freely)
- Use revsets liberally (query, don't manually track)
- Clean up incrementally (jj undo backs out any step)