jj-skill/revsets.md
2025-11-03 14:13:41 -05:00

19 KiB

Jujutsu Revsets - Complete Reference

Revsets are a functional language in jujutsu for selecting and querying commits. This document provides comprehensive coverage of revset syntax, symbols, operators, functions, and practical usage patterns.

Overview

The revset language (inspired by Mercurial) enables powerful commit selection across jujutsu commands. Expressions in this language are called "revsets" and are fundamental to working effectively with jujutsu.

The language consists of:

  • Symbols - References to specific commits or sets of commits
  • Operators - Ways to traverse and combine commit sets
  • Functions - Queries and filters for finding commits

Core Symbols

Working Copy References

@

The current working copy commit.

jj log -r @              # Show working copy commit
jj diff -r @             # Diff of working copy
jj new @-                # Create new commit on parent of @

<workspace>@

Working copy in a named workspace.

jj log -r main@          # Working copy in "main" workspace
jj diff -r feature@      # Working copy in "feature" workspace

Remote References

<name>@<remote>

Remote-tracking bookmark.

jj log -r main@origin    # Remote tracking branch
jj rebase -d main@origin # Rebase onto remote main
jj diff --from main@origin --to @  # Compare to remote

Commit Identifiers

Change IDs and Commit IDs

Full hexadecimal IDs or unique prefixes.

jj log -r k              # Unique prefix
jj log -r kmkuslsw       # Longer prefix
jj log -r kmkuslswpqwq   # Full change ID
jj show a1b2c3d          # Commit ID prefix

Resolution order: Tags → Bookmarks → Git refs → Commit IDs

Special Symbols

root()

The root commit (virtual commit at base of history).

jj log -r root()         # Show root commit
jj log -r ::@            # All ancestors including root

Operators

Operators traverse commit history and combine commit sets.

Ancestry Operators

x- (Parents)

Immediate parent(s) of x.

jj log -r @-             # Parent of working copy
jj log -r @--            # Grandparent
jj show @-               # Show parent commit
jj edit @-               # Edit parent commit

Multiple parents (merge commits):

jj log -r @-+            # Children of all parents (siblings)

x+ (Children)

Immediate children of x.

jj log -r @+             # Children of working copy
jj log -r main@origin+   # Children of remote main

::x (Ancestors)

All ancestors of x, including x itself.

jj log -r ::@            # All ancestors of working copy
jj log -r ::main         # All ancestors of main
jj log -r ::@-           # Ancestors up to parent

x:: (Descendants)

All descendants of x, including x itself.

jj log -r @::            # Working copy and all descendants
jj log -r main::         # main and its descendants

x::y (DAG Range)

Commits reachable from y through ancestor relationships with x.

jj log -r main::@        # Commits from main to @
jj log -r @-::@          # Just @ (parent to working copy)

::x with depth

Ancestors with limited depth.

jj log -r ancestors(@, 5)   # Last 5 ancestors
jj log -r ancestors(@, 1)   # Just parent (same as @-)

Range Operators

..x (Ancestors excluding root)

Ancestors of x, excluding the root commit.

jj log -r ..@            # Ancestors of @ without root
jj log -r ..main         # History up to main

x.. (Non-ancestors)

x and its descendants, excluding ancestors.

jj log -r @..            # Working copy and descendants
jj log -r main..         # Everything not ancestral to main

x..y (Range)

Commits in y but not in x (y's descendants minus x's ancestors).

jj log -r main..@        # Commits added since main
jj log -r @-..@          # Just @ (changes since parent)
jj log -r @..main        # Commits in main not in @

Set Operators

x | y (Union)

Commits in either x or y (or both).

jj log -r '@- | @'       # Parent and working copy
jj log -r 'main | feature'  # Both branches
jj log -r 'bookmarks() | tags()'  # All bookmarks and tags

x & y (Intersection)

Commits in both x and y.

jj log -r 'author(alice) & description(bug)'  # Alice's bug fixes
jj log -r 'mine() & ::main'  # My commits in main's history

x ~ y (Difference)

Commits in x but not in y.

jj log -r 'all() ~ main'  # All commits except main
jj log -r '@:: ~ @'       # Descendants excluding working copy
jj log -r 'mine() ~ ::main'  # My commits not yet in main

~x (Complement)

All commits except those in x.

jj log -r '~@'           # Everything except working copy
jj log -r '~main::'      # Everything not descended from main

Operator Precedence

From highest to lowest:

  1. - (parents), + (children)
  2. ::, .. (DAG ranges)
  3. ~ (negation/complement)
  4. & (intersection)
  5. ~ (set difference)
  6. | (union)

Use parentheses to override:

jj log -r '(main | feature) & author(alice)'
jj log -r 'main::(@ | feature)'

Functions

Functions query and filter commits based on various criteria.

Traversal Functions

ancestors(x[, depth])

All ancestors of x, optionally limited by depth.

jj log -r 'ancestors(@)'        # All ancestors
jj log -r 'ancestors(@, 5)'     # 5 generations back
jj log -r 'ancestors(main, 10)' # 10 commits before main

descendants(x)

All descendants of x, including x.

jj log -r 'descendants(main)'   # main and everything after
jj log -r 'descendants(@-)'     # Parent and its descendants

connected(x)

Commits connected to x through parents and children.

jj log -r 'connected(@)'        # Connected component containing @

reachable(x, domain)

Commits in domain that are reachable from x.

jj log -r 'reachable(@, all())'  # All reachable from @

Set Functions

all()

All commits in the repository.

jj log -r 'all()'               # Entire history
jj log -r 'all() ~ ::main'      # Everything not in main's history

none()

Empty set (no commits).

jj log -r 'none()'              # No output
jj log -r 'none() | @'          # Just @ (contrived example)

heads(x)

Commits in x that have no children in x.

jj log -r 'heads(all())'        # All branch tips
jj log -r 'heads(main::)'       # Tips descended from main

roots(x)

Commits in x that have no parents in x.

jj log -r 'roots(main..@)'      # First commits after branching from main
jj log -r 'roots(bookmarks())'  # Root commits of bookmarked branches

latest(x[, count])

Latest (newest) commits from x, optionally limited.

jj log -r 'latest(all())'       # Most recent commit
jj log -r 'latest(all(), 10)'   # 10 most recent commits
jj log -r 'latest(author(alice), 5)'  # Alice's 5 latest commits

Bookmark and Tag Functions

bookmarks([pattern])

Commits pointed to by bookmarks, optionally filtered by pattern.

jj log -r 'bookmarks()'         # All bookmarked commits
jj log -r 'bookmarks(main)'     # Main bookmark
jj log -r 'bookmarks(glob:feature-*)'  # Feature branches

Pattern types: substring:, exact:, glob:, regex: (append -i for case-insensitive)

remote_bookmarks([pattern[, [remote=]pattern]])

Remote-tracking bookmarks, optionally filtered.

jj log -r 'remote_bookmarks()'  # All remote bookmarks
jj log -r 'remote_bookmarks(main)'  # main on all remotes
jj log -r 'remote_bookmarks(main, origin)'  # main@origin
jj log -r 'remote_bookmarks(glob:feature-*, origin)'  # Feature branches on origin

tags([pattern])

Commits with tags, optionally filtered.

jj log -r 'tags()'              # All tagged commits
jj log -r 'tags(v1.0.0)'        # Specific tag
jj log -r 'tags(glob:v1.*)'     # All v1.x tags

git_refs()

All Git references.

jj log -r 'git_refs()'          # All Git refs

git_head()

Git's HEAD reference.

jj log -r 'git_head()'          # Show Git HEAD

tracked_remote_bookmarks([bookmark_pattern[, [remote=]remote_pattern]])

Bookmarks with remote tracking configured.

jj log -r 'tracked_remote_bookmarks()'  # All tracked bookmarks
jj log -r 'tracked_remote_bookmarks(main)'  # Tracked main branches

untracked_remote_bookmarks([bookmark_pattern[, [remote=]remote_pattern]])

Remote bookmarks without local tracking.

jj log -r 'untracked_remote_bookmarks()'  # Untracked remotes

Author and Committer Functions

author(pattern)

Commits where author matches pattern.

jj log -r 'author(alice)'       # Alice's commits
jj log -r 'author("Alice Smith")' # Exact name
jj log -r 'author(glob:*@example.com)'  # By email domain
jj log -r 'author(regex:^A)'    # Names starting with A

Pattern matching supports: substring:, exact:, glob:, regex: (with -i suffix for case-insensitive)

committer(pattern)

Commits where committer matches pattern.

jj log -r 'committer(bot)'      # Commits by bot
jj log -r 'committer(glob:*@github.com)'  # GitHub committers

mine()

Commits authored or committed by the current user.

jj log -r 'mine()'              # All my commits
jj log -r 'mine() & ::main'     # My commits in main
jj log -r 'mine() ~ ::main'     # My commits not in main

Description Functions

description(pattern)

Commits with description matching pattern.

jj log -r 'description(bug)'    # Commits mentioning "bug"
jj log -r 'description(exact:Fix bug #123)'  # Exact match
jj log -r 'description(glob:*BREAKING*)'  # Contains "BREAKING"
jj log -r 'description(regex:^fix:)'  # Conventional commit fixes

subject(pattern)

Commits with first line of description matching pattern.

jj log -r 'subject(feat:)'      # Feature commits
jj log -r 'subject(glob:*WIP*)'  # WIP commits

File Functions

files(pattern)

Commits modifying files matching pattern.

jj log -r 'files(glob:*.rs)'    # Commits changing Rust files
jj log -r 'files(src/main.rs)'  # Commits changing specific file
jj log -r 'files(glob:**/test_*)'  # Commits changing test files

Uses fileset syntax (see filesets.md).

diff_contains(pattern[, [files=]fileset])

Commits with diff containing pattern, optionally in specific files.

jj log -r 'diff_contains(TODO)'  # Commits adding/removing TODO
jj log -r 'diff_contains(bug, files=glob:*.rs)'  # "bug" in Rust files
jj log -r 'diff_contains(regex:\bfix\b)'  # Word "fix" in diff

State Functions

empty()

Commits with no changes (empty diffs).

jj log -r 'empty()'             # All empty commits
jj log -r 'mine() & empty()'    # My empty commits
jj abandon $(jj log -r 'empty()' -T commit_id --no-graph)  # Remove empty commits

conflict()

Commits with unresolved conflicts.

jj log -r 'conflict()'          # Find conflicted commits
jj resolve $(jj log -r 'conflict()' -T commit_id --no-graph | head -1)  # Resolve first

immutable()

Commits considered immutable (configured in settings).

jj log -r 'immutable()'         # Show immutable commits
jj log -r 'all() ~ immutable()' # Only mutable commits

present(x)

Returns x if it exists, otherwise empty set.

jj log -r 'present(some-branch)' # Branch if it exists
jj rebase -d 'present(main) | @-'  # Use main if exists, else parent

Useful for scripting to avoid errors when references don't exist.

Visibility Functions

visible_heads()

Commits that are visible heads.

jj log -r 'visible_heads()'     # All visible heads

immutable_heads()

Heads of immutable commits.

jj log -r 'immutable_heads()'   # Immutable heads

Pattern Matching

String patterns in functions support multiple matching modes.

Pattern Prefixes

  • substring: - Default, matches if pattern appears anywhere
  • exact: - Exact match only
  • glob: - Unix-style glob patterns
  • regex: - Regular expression matching

Add -i suffix for case-insensitive matching:

  • substring-i:, exact-i:, glob-i:, regex-i:

Examples

# Substring (default)
jj log -r 'author(alice)'
jj log -r 'author(substring:alice)'  # Equivalent

# Exact match
jj log -r 'author(exact:Alice Smith)'
jj log -r 'description(exact:Fix bug #123)'

# Glob patterns
jj log -r 'author(glob:*@example.com)'
jj log -r 'bookmarks(glob:feature-*)'
jj log -r 'files(glob:**/*.rs)'

# Regular expressions
jj log -r 'description(regex:^feat:)'
jj log -r 'author(regex:\d+\+.*@users.noreply.github.com)'

# Case-insensitive
jj log -r 'author(substring-i:ALICE)'
jj log -r 'description(glob-i:*breaking*)'

Practical Examples

Finding Commits

# Recent work
jj log -r 'latest(mine(), 10)'  # My 10 most recent commits

# Commits by criteria
jj log -r 'author(alice) & description(bug)'  # Alice's bug fixes
jj log -r 'mine() & conflict()'  # My conflicted commits
jj log -r 'files(glob:*.rs) & description(perf)'  # Rust performance commits

# Branch comparisons
jj log -r 'main..@'             # My commits not in main
jj log -r 'main..feature'       # Commits in feature not in main
jj log -r 'main@origin..'       # All commits not pushed

Working with Branches

# View all branches
jj log -r 'bookmarks()'

# Active development branches
jj log -r 'heads(all()) ~ remote_bookmarks()'

# Branches needing updates
jj log -r 'bookmarks() ~ ::remote_bookmarks(main, origin)'

# Merged branches
jj log -r 'bookmarks() & ::main'

History Analysis

# Commits touching specific files
jj log -r 'files(src/auth.rs)'

# Recent changes to tests
jj log -r 'latest(files(glob:**/*_test.rs), 20)'

# Commits between tags
jj log -r 'tags(v1.0.0)..tags(v2.0.0)'

# Commits not yet tagged
jj log -r 'heads(all()) ~ ::tags()'

Commit Cleanup

# Find empty commits
jj log -r 'empty()'

# Find WIP commits
jj log -r 'description(glob:*WIP*)'

# Commits needing attention
jj log -r 'conflict() | empty() | description(glob:*TODO*)'

Working with Remotes

# Commits not pushed
jj log -r 'mine() ~ ::remote_bookmarks()'

# Remote updates
jj log -r 'remote_bookmarks() ~ ::bookmarks()'

# Diverged branches
jj log -r '(main ~ ::main@origin) | (main@origin ~ ::main)'

Complex Queries

Multi-Criteria Searches

# My recent bug fixes in Rust code
jj log -r '
  latest(
    mine()
    & description(glob:*fix*|*bug*)
    & files(glob:**/*.rs),
    20
  )
'

# Commits by team members on feature branches
jj log -r '
  (author(alice) | author(bob) | author(carol))
  & bookmarks(glob:feature-*)::
  ~ ::main
'

# Risky commits (large changes, no tests)
jj log -r '
  files(glob:src/**/*.rs)
  ~ files(glob:**/*_test.rs)
  & description(regex:(?i)\b(refactor|rewrite)\b)
'

Conditional Selection

# Use main@origin if it exists, otherwise main
jj rebase -d 'present(main@origin) | main'

# Latest stable release or head
jj log -r 'present(tags(glob:v*.*.0)) | heads(all())'

Graph Queries

# All commits between branches
jj log -r 'main::feature ~ (::main | feature::)'

# Divergent changes
jj log -r '(main ~ ::feature) | (feature ~ ::main)'

# Common ancestors
jj log -r '::main & ::feature'

Best Practices

1. Start Simple, Then Refine

# Start broad
jj log -r 'mine()'

# Add date constraints
jj log -r 'latest(mine(), 20)'

# Add content filter
jj log -r 'latest(mine() & description(feature), 20)'

2. Use Readable Multi-Line Queries

# Hard to read
jj log -r 'latest(mine() & (description(bug) | description(fix)) & files(glob:src/**/*.rs) ~ ::main, 10)'

# Readable
jj log -r '
  latest(
    mine()
    & (description(bug) | description(fix))
    & files(glob:src/**/*.rs)
    ~ ::main,
    10
  )
'

3. Create Aliases for Common Queries

Edit config with jj config edit --user:

[revset-aliases]
# My commits not in main
work = "mine() ~ ::main"

# Commits needing attention
attention = "conflict() | empty() | description(glob:*WIP*) | description(glob:*TODO*)"

# Recent changes
recent = "latest(all(), 20)"

# Unpushed work
unpushed = "mine() ~ ::remote_bookmarks()"

Use in commands:

jj log -r work
jj log -r attention
jj log -r recent

4. Use present() for Robustness

# Fails if branch doesn't exist
jj rebase -d some-branch

# Safe version
jj rebase -d 'present(some-branch) | @-'

5. Combine with Templates for Insight

# Show commit IDs of specific commits
jj log -r 'conflict()' -T commit_id --no-graph

# Count commits
jj log -r 'mine() ~ ::main' --no-graph | wc -l

# Custom format for scripting
jj log -r 'latest(mine(), 5)' -T 'commit_id ++ " " ++ description.first_line() ++ "\n"'

Troubleshooting

No Commits Matched

# Check individual parts
jj log -r 'author(alice)'       # Does author filter work?
jj log -r 'description(bug)'    # Does description filter work?
jj log -r 'author(alice) & description(bug)'  # Combine

# Verify symbols exist
jj bookmark list                # Check bookmark names
jj log -r 'bookmarks()'         # See all bookmarks

Unexpected Results

# Use `all()` to understand set size
jj log -r 'all() ~ ::main'      # Everything not in main

# Break down complex queries
jj log -r 'mine()'              # Step 1
jj log -r 'mine() ~ ::main'     # Step 2
jj log -r 'latest(mine() ~ ::main, 10)'  # Step 3

Performance Issues

# Large repositories may be slow with broad queries
jj log -r 'all()'               # Can be slow

# Optimize by limiting scope
jj log -r 'latest(all(), 100)'  # Faster
jj log -r '::@'                 # Just ancestors
jj log -r 'ancestors(@, 50)'    # Limited depth

Integration with Commands

Revsets work with most jujutsu commands:

Viewing History

jj log -r '<revset>'            # Show log
jj show -r '<revset>'           # Show details
jj diff -r '<revset>'           # Show changes
jj evolog -r '<revset>'         # Show evolution log

Modifying Commits

jj edit <revset>                # Edit commit
jj squash -r <revset>           # Squash commit
jj abandon <revset>             # Abandon commits
jj describe -r <revset>         # Set description

Rebasing

jj rebase -d <revset>           # Rebase destination
jj rebase -r <revset> -d <dest> # Rebase specific commit
jj rebase -s <revset> -d <dest> # Rebase source and descendants

Branching

jj bookmark set <name> -r <revset>  # Set bookmark
jj new <revset>                     # New commit on base

Summary

Revsets are the foundation of powerful commit selection in jujutsu:

  • Symbols: @, root(), commit IDs, bookmarks, tags
  • Operators: -/+ (parents/children), :: (ancestors/descendants), |/&/~ (set operations)
  • Functions: mine(), author(), description(), files(), conflict(), empty(), etc.
  • Patterns: substring:, exact:, glob:, regex: (with -i for case-insensitive)
  • Best practices: Start simple, use aliases, combine with templates
  • Integration: Works across all jujutsu commands

For file-based filtering, see filesets.md. For output customization, see templating.md.

Master revsets to unlock jujutsu's full potential for commit management and history navigation.