Jujutsu (jj) VCS - Read-Only Navigation Skill
Use this skill when asked to explore, navigate, or research repository history using jj. This skill is for read-only operations only.
CRITICAL: This skill permits ONLY read-only commands. NO MUTATING COMMANDS are allowed.
Core Concepts (Key Differences from Git)
Change ID vs Commit ID
- •Commit ID: Like Git's SHA - changes when a commit is rewritten (20 bytes, hex format)
- •Change ID: Stable identifier for a change that persists across rewrites (displayed as 12 letters k-z)
- •Prefer change IDs when referencing commits, as they survive rebases and amendments
Working Copy is a Commit
Unlike Git where the working copy is separate, in jj the working copy IS a commit (@). Every edit automatically amends this commit.
Bookmarks (not Branches)
- •jj uses "bookmarks" - named pointers to commits (similar to Git branches)
- •Bookmarks do NOT auto-advance when you create new commits
- •Bookmarks DO follow when commits are rewritten/rebased
- •No concept of "current branch" - you're always on a working-copy commit
Anonymous Branches
Commits without bookmarks form "anonymous branches" - jj keeps them visible until explicitly abandoned.
Private Changes
Changes with descriptions prefixed private: or wip: cannot be pushed to remotes. Example: private: WORKING
Workspaces
Multiple working copies from the same repository (like Git worktrees). Each workspace has its own @. Reference another workspace's working copy with <workspace>@.
User's Workflow Patterns
IMPORTANT: The workflow varies by project. Check jj st and jj lt to understand the current repo structure. When in doubt about what workflow is in use, ASK the user.
Megamerge (Large Projects)
Used in larger projects where multiple features are developed simultaneously:
- •A working change (typically
private: WORKING) has multiple parents - •This forms a union of several branches in one working copy
- •The megamerge change itself is NOT part of any branch
- •When navigating, examine its parents (
@-) to see the actual branches being worked on - •Identified by:
@having multiple parents, description likeprivate: WORKING
Simple Linear (Smaller Projects / Alternative Workspaces)
- •
@sits at the head of a single branch/bookmark - •Used for focused work, long-running migrations, or simpler projects
- •Navigate normally without megamerge considerations
Detecting the Workflow
Run jj st or jj lt first. If @ has:
- •Multiple parents with a
private:description → megamerge workflow - •Single parent → simple linear workflow
- •If unclear → ask the user
User's Configured Aliases
jj lt [revset] - Log to Trunk (PREFERRED)
- •Default: logs from
@back to trunk - •With argument (typically a bookmark): logs between trunk ancestors and
@where the given revset is involved - •Prefer this over raw
jj logwhen exploring around bookmarks - it shows the full picture including descendant revisions up to@ - •Example:
jj lt my-featureshows all relevant context for themy-featurebookmark
jj dt - Diff to Trunk
Shows diff from trunk to current state. Always uses git format.
Diff Flag
Always use --git flag with jj diff or diff-based commands.
Allowed Read-Only Commands
Status & Current State
jj st # Status of working copy - RUN THIS FIRST jj lt # Log from @ to trunk (preferred default) jj lt <bookmark> # Log around a bookmark with full context to @ jj log -r '@-' # Parents of @ (useful for megamerge)
Viewing Changes
jj show # Show current commit (@) jj show <rev> # Show specific revision jj diff --git # Diff of @ vs parents jj diff -r <rev> --git # Diff of specific revision jj diff --from <rev1> --to <rev2> --git # Diff between revisions
jj show does NOT accept file path arguments. To filter a revision's diff
to specific files, use jj diff -r <rev> --git -- <paths>.
IMPORTANT: -r and --from/--to are mutually exclusive in jj diff. Use one or the other:
- •
jj diff -r <rev>— show what changed IN that revision (vs its parents) - •
jj diff --from <A> --to <B>— show difference BETWEEN two revisions
Common use cases:
- •"What did this one commit change?" →
jj diff -r <rev> --git - •"All changes on a branch vs trunk/develop?" →
jj diff --from trunk() --to <branch_tip> --git - •"Cumulative diff of a branch?" →
jj diff --from <base> --to <tip> --git(NOT-r)
Common mistakes:
- •Using
-r <branch_tip>when you want the cumulative branch diff. This only shows that single commit's changes, not all commits since the branch diverged. - •Using
-r <complex_revset>with a revset that has gaps.jj diff -rrequires a contiguous range. If you get "Cannot diff revsets with gaps in", use--from/--toinstead.
Exploring History
jj evolog # How current change evolved over time jj evolog -r <rev> # Evolution of specific change jj op log # Operation log (undo history)
evolog templates use a different type — see "evolog Templates" section below.
When to Use evolog
evolog shows the evolution of a single change — all the historical versions of the same change ID as it was amended, rebased, or otherwise rewritten. Each entry is a snapshot of what the commit looked like at a point in time.
Use evolog when asked about:
- •"What changed since I last worked on this?" / "changes over the last session"
- •"Show me the history of edits to this commit/branch"
- •"Find when a specific modification was made"
- •"How did this working copy evolve?"
- •Finding a baseline commit ID to diff against (e.g., "diff since my last edit")
Typical workflow for "show changes since X":
- •Run
jj evologto see the evolution timeline with commit IDs and timestamps - •Identify the commit ID that represents the "before" state (look for time gaps, or specific timestamps)
- •Use
jj diff --from <old-commit-id> --to @ --gitto see all changes since then
Example: User says "show me what changed since I modified the instructions"
- •
jj evolog— find the commit ID from when they made their edit (look for timestamp/gap) - •
jj diff --from <that-commit-id> --to @ --git— see all changes since
This is different from jj log which shows the commit graph, and jj op log which shows operations (undo history).
Bookmarks & Remotes
jj bookmark list # List local bookmarks jj bookmark list --all-remotes # Include remote bookmarks jj bookmark list --tracked # Show tracked remote bookmarks
Files
jj file list # List files in @ jj file list -r <rev> # List files in specific revision
Workspaces
jj workspace list # List all workspaces jj log -r '<workspace>@' # See another workspace's working copy
Revset Syntax Quick Reference
Symbols
- •
@- working copy commit (this workspace) - •
<name>@- working copy of another workspace - •
@-- parent(s) of @ (see warning below) - •
root()- virtual root commit - •
trunk()- trunk bookmark (usually main@origin)
WARNING about @- with merges: When @ is a merge commit (multiple parents), @- returns ALL parents, which may not be what you want in a revset range. For example, branch_tip::@- may give unexpected results if @- includes commits not in the branch's ancestry. Be explicit: use ::branch_tip & ~::trunk() to enumerate branch commits reliably.
Operators
- •
x-- parents of x - •
x+- children of x - •
::x- ancestors of x (inclusive) - •
x::- descendants of x (inclusive) - •
x..y- ancestors of y excluding ancestors of x (like Git's x..y) - •
x::y- ancestors of y that are descendants of x (ancestry-path) - •
x & y- intersection - •
x | y- union - •
~x- not x
Common Functions
- •
bookmarks()- all bookmark targets - •
remote_bookmarks()- all remote bookmark targets - •
heads(x)- heads within x - •
roots(x)- roots within x - •
description(pattern)- commits matching description - •
author(pattern)- commits by author - •
mine()- commits by current user - •
empty()- empty commits - •
conflicts()- commits with conflicts - •
merges()- merge commits - •
working_copies()- working copy commits across all workspaces
Custom Templates
Use -T to customize output. Key points:
Accessing Timestamps
Timestamps are accessed via signature objects, NOT as top-level keywords:
# WRONG - these don't exist:
jj log -T 'commit_timestamp.ago()'
jj log -T 'format_timestamp(commit_timestamp, "%Y-%m-%d")'
# CORRECT:
jj log -T 'committer.timestamp().ago()'
jj log -T 'author.timestamp().format("%Y-%m-%d")'
Common Template Methods
- •
committer.timestamp()/author.timestamp()→ Timestamp object - •
.ago()→ relative time string ("2 days ago") - •
.format("%Y-%m-%d")→ custom strftime format - •
.local()/.utc()→ timezone conversion
Useful Template Examples
# Compact one-line format with date
jj log -T 'change_id.short() ++ " " ++ committer.timestamp().ago() ++ " " ++ description.first_line() ++ "\n"'
# Machine-readable with ISO date
jj log -T 'commit_id ++ " " ++ committer.timestamp().format("%Y-%m-%d") ++ "\n"'
evolog Templates
jj evolog uses a different template type (CommitEvolutionEntry) than jj log (Commit). You must use self.commit() to access commit properties:
# WRONG - these don't work in evolog:
jj evolog -T 'change_id.short() ...' # Error: Keyword doesn't exist
jj evolog -T 'commit_id.short() ...' # Error: Keyword doesn't exist
jj evolog -T 'commit().commit_id().short()' # Error: Function doesn't exist
# CORRECT - use self.commit() to access the wrapped commit:
jj evolog -T 'self.commit().commit_id().short() ++ " " ++ self.commit().committer().timestamp().format("%H:%M") ++ "\n"'
Available CommitEvolutionEntry methods:
- •
self.commit()→ theCommitobject (then use normal commit methods) - •
self.operation()→ the operation where this version was created - •
self.predecessors()→ list of predecessor commits - •
self.inter_diff([files])→ changes between this and predecessor versions
Getting Help
Use jj help -k <keyword> (or --keyword) for detailed inline documentation. Running it without a keyword shows available topics (currently: bookmarks, config, filesets, glossary, revsets, templates, tutorial — if this list has changed, update this skill file).
Output is markdown and can be lengthy. Filter to headings first to find relevant sections:
jj help -k templates | grep '^#' # See section structure jj help -k revsets | grep -A2 '^## ' # Headings with brief context
Example Queries
First: Understand the Repo
jj st # What's the current state? jj lt # What's the history to trunk?
Catching Up on a Branch
When asked to "catch up" or "see what changed" on a branch, always start with jj lt <bookmark> before using jj diff:
- •
jj lt <bookmark>- See commit structure, messages, and context first - •Then
jj diff --from <base> --to <tip> --gitif you need the full diff
Why: The log shows what was done and why (commit messages), while diff only shows how. Understanding the sequence and intent first makes the diff more meaningful.
Megamerge Navigation (when applicable)
jj log -r '@-' # See what branches the megamerge combines jj lt my-feature # Explore a specific bookmark with full context jj diff --from trunk() --to @ --git # See all work since trunk jj log -r '@-' -- path/to/file # Find which parent branch contains a file change
Extracting Changes from Megamerge for Push
To extract specific files from the megamerge working copy into a new change ready for push:
jj split -A develop -B @ -m "commit message" path/to/file1 path/to/file2
Flags explained:
- •
-A develop(insert-after): The new change becomes a child ofdevelop - •
-B @(insert-before): The new change becomes a parent of the megamerge - •Result:
develop → new-change → megamerge
IMPORTANT: File paths are relative to your current working directory, not the repo root. Check jj st output for the correct relative paths. If jj st shows ../test/file.js, use that path, not workspace/@causal/test/file.js.
Recovering from workspace update-stale Divergence
When jj workspace update-stale reconciles divergent operations, it may create a new empty working copy while the version with actual work becomes an orphaned divergent copy. Symptoms: @ is empty, change ID shows as <id>/2 (divergent).
Diagnosis:
jj log -r 'change_id(<change_id>)' # List all divergent versions
Recovery: The simplest fix is to jj edit to the version that has the work:
jj edit <change_id>/0 # Edit the version with actual content
Do NOT reach for jj op restore — it's overkill when the work is right there in a divergent copy. op restore rewinds the entire operation history; jj edit just points the working copy at the right version.
Cross-Workspace
jj workspace list # See all workspaces jj log -r 'working_copies()' # See what all workspaces are doing jj diff --from 'other-workspace@' --to @ --git # Compare workspaces
Non-Interactive Split Tool
See split-tool.md for the manifest-driven jj-split-tool.sh
that enables hunk-level splits without a TUI, via jj split --tool.
FORBIDDEN (Mutating Commands)
DO NOT USE any of these commands - they modify repository state:
- •
jj new,jj commit,jj describe - •
jj edit,jj squash,jj split - •
jj rebase,jj abandon - •
jj bookmark create/move/delete/track - •
jj git push/fetch - •
jj undo,jj op restore - •
jj workspace add/forget - •Any other command that modifies commits, bookmarks, or working copy