initial commit

This commit is contained in:
sloane 2025-11-03 14:13:34 -05:00
commit c4fa6e07cb
No known key found for this signature in database
4 changed files with 2632 additions and 0 deletions

368
SKILL.md Normal file
View file

@ -0,0 +1,368 @@
---
name: jj
description: Expert guidance for jujutsu (jj) version control system - modern VCS with first-class conflicts, change tracking, and powerful revsets
---
# Jujutsu (jj) Version Control System Skill
You are an expert in jujutsu (jj), a modern version control system. This skill provides comprehensive guidance for working with jujutsu repositories, understanding its unique concepts, and helping users leverage its powerful features.
## Core Concepts
### Changes vs Commits
- Jujutsu introduces "changes" as commits with stable identifiers that persist even when commits are rewritten
- Each change has both a change ID (stable) and a commit ID (changes with rewrites)
- This enables tracking changes across rebases and amendments
### Working Copy as a Commit
- The working copy is an actual commit that auto-amends with each operation
- Most jj commands automatically commit working-copy changes if modified
- No need for explicit staging (like `git add`) - files are automatically tracked
- Use `jj describe` to set the commit message for the working copy
### First-Class Conflicts
- Conflicts are recorded directly in commits rather than blocking operations
- Operations like rebase/merge succeed and record conflict state
- Conflicts can be resolved later: check out the conflicted commit, resolve, and amend
- Descendants of rewritten commits automatically rebase
## Essential Commands
### Repository Setup
```bash
jj git clone <url> # Clone a Git repository
jj git init --git-repo=. # Initialize in existing Git repo
jj init --git # Create new repo with Git backend
```
### Viewing State
```bash
jj st # Show working copy status
jj log # Show commit history
jj log -r '<revset>' # Show filtered commits
jj op log # Show operation history
jj show <commit> # Show commit details
jj diff # Show working copy changes
jj diff -r <revset> # Show changes in specific commits
```
### Making Changes
```bash
jj describe # Set commit message (opens editor)
jj describe -m "message" # Set commit message inline
jj new # Create new commit on top of current
jj new <base> # Create new commit on specified base
jj edit <commit> # Edit an existing commit
jj abandon <commit> # Abandon a commit (preserve children)
```
### Moving Changes
```bash
jj squash # Move changes from @ into parent
jj squash -r <commit> # Squash specific commit into parent
jj squash -i # Interactively select changes to squash
jj split # Split current commit into multiple
jj move --from <src> --to <dst> # Move changes between commits
jj diffedit # Interactively edit changes
```
### Branching and Bookmarks
```bash
jj bookmark create <name> # Create bookmark at current commit
jj bookmark set <name> -r <commit> # Set bookmark to specific commit
jj bookmark list # List all bookmarks
jj bookmark delete <name> # Delete a bookmark
jj bookmark track <name>@<remote> # Track remote bookmark
```
### Rebasing
```bash
jj rebase -d <destination> # Rebase current commit
jj rebase -r <commit> -d <dest> # Rebase specific commit
jj rebase -s <source> -d <dest> # Rebase source and descendants
jj rebase -b <branch> -d <dest> # Rebase branch (all ancestors)
```
### Conflict Resolution
```bash
jj resolve # Interactively resolve conflicts
jj resolve --list # List conflicted files
jj resolve <file> # Resolve specific file
```
### Working with Git
```bash
jj git fetch # Fetch from remotes
jj git push # Push changes
jj git push --change <id> # Push specific change
jj git push --bookmark <name> # Push specific bookmark
jj git remote add <name> <url> # Add remote
jj git remote list # List remotes
jj git export # Export to Git (updates Git refs)
jj git import # Import from Git
```
### Undo and Recovery
```bash
jj undo # Undo last operation
jj op undo <operation> # Undo specific operation
jj op restore <operation> # Restore to specific operation
jj op abandon <operation> # Abandon operation from log
```
### Multi-Workspace
```bash
jj workspace add <path> # Create new workspace
jj workspace list # List workspaces
jj workspace forget <name> # Remove workspace
jj workspace update-stale # Update stale working copy
```
## Important Symbols and Operators
### Revset Symbols
- `@` - The working copy commit
- `<workspace>@` - Working copy in another workspace
- `<name>@<remote>` - Remote-tracking bookmark
- `root()` - The root commit
- Commit/Change IDs - Full or unique prefixes
### Revset Operators
- `x-` - Parents of x
- `x+` - Children of x
- `x::` - Descendants of x (inclusive)
- `::x` - Ancestors of x (inclusive)
- `x..` - Non-ancestors of x (x and descendants minus ancestors)
- `..x` - Ancestors of x excluding root
- `x & y` - Intersection
- `x | y` - Union
- `x ~ y` - Difference (x but not y)
- `~x` - Complement (everything except x)
### Fileset Operators
- `~x` - Negation (everything except x)
- `x & y` - Intersection
- `x ~ y` - Difference
- `x | y` - Union
## Common Workflows
### Daily Development
```bash
# Start new work
jj new main # Create new commit based on main
jj describe -m "Add feature" # Describe your work
# Make changes to files
jj st # Check status
jj diff # Review changes
# Create another commit
jj new # Start fresh commit
jj describe -m "Add tests"
# Make more changes
# Update existing commit
jj edit <commit-id> # Edit specific commit
# Make changes
jj new # Return to working on new commit
```
### Interactive Editing
```bash
# Move some changes from working copy to parent
jj squash -i # Select which changes to squash
# Split a commit
jj edit <commit> # Edit the commit
jj split # Interactively split changes
# Edit changes visually
jj diffedit -r <commit> # Edit commit's changes in difftool
```
### Working with Branches
```bash
# Create feature branch
jj bookmark create feature
jj new # Start working
# Rebase on updated main
jj git fetch
jj rebase -d main@origin # Rebase current commit on remote main
# Push branch
jj git push --bookmark feature
```
### Resolving Conflicts
```bash
# After a rebase creates conflicts
jj log -r 'conflict()' # Find conflicted commits
jj edit <conflicted-commit> # Edit the conflicted commit
jj resolve # Interactively resolve conflicts
# Or manually edit files
jj new # Move back to working copy
```
### Recovering from Mistakes
```bash
# Undo last operation
jj undo
# View operation history
jj op log
# Restore to specific point
jj op restore <operation-id>
# Abandon unwanted changes
jj abandon <commit>
```
## Best Practices
### When to Use Commands
1. **`jj new`** - Start new work or move away from edited commit
2. **`jj edit`** - Modify existing commits directly
3. **`jj squash`** - Combine changes (default: move @ into parent)
4. **`jj squash -i`** - Selectively move changes between commits
5. **`jj describe`** - Set commit messages
6. **`jj rebase`** - Move commits to new bases
### Understanding Auto-tracking
- New files are automatically tracked (no `jj add` needed)
- Deleted files are automatically removed
- Working copy changes auto-commit on most commands
- Use `.gitignore` to exclude files
### Conflict Management
- Don't fear conflicts - they're stored in commits
- Resolve conflicts when convenient
- Descendants automatically rebase after resolution
- Use `jj resolve` for interactive resolution or edit files manually
### Operation Log
- Every command creates an operation in the log
- Operations can be undone/restored
- Use `jj op log` to understand repository history
- Operations include timestamps and descriptions
## Advanced Features
### Revsets (Detailed reference in revsets.md)
Powerful query language for selecting commits:
```bash
jj log -r 'author(name) & description(keyword)'
jj log -r 'bookmarks() & ~remote_bookmarks()'
jj log -r 'ancestors(@, 5)' # Last 5 ancestors
jj log -r 'mine() & ~::main' # My commits not in main
```
### Filesets (Detailed reference in filesets.md)
Precise file selection:
```bash
jj diff '~Cargo.lock' # Exclude file
jj split 'glob:"src/**/*.rs"' # Only Rust files
jj diff 'root:src & ~glob:"**/*.test.ts"' # Source minus tests
```
### Templates (Detailed reference in templating.md)
Customize output formatting:
```bash
jj log -T 'commit_id.short() ++ " " ++ description.first_line()'
jj log -T 'if(conflict(), "⚠️ ", "") ++ description'
```
### Configuration
Edit config with `jj config edit --user`:
```toml
[user]
name = "Your Name"
email = "your@email.com"
[ui]
default-command = "log"
diff-editor = "vimdiff"
merge-editor = "meld"
[aliases]
l = ["log", "-r", "(main..@):: | (main..@)-"]
```
## Comparison with Git
### Key Differences
1. **No staging area** - Changes in working copy are automatically tracked
2. **Working copy is a commit** - Auto-amends instead of separate staging
3. **Conflicts are first-class** - Stored in commits, resolved later
4. **Auto-rebase** - Descendants automatically update when parents change
5. **Change IDs** - Stable identifiers across rewrites
6. **Operation log** - Complete undo/redo history
### Command Mapping
- `git add` → Not needed (automatic)
- `git commit``jj describe` + `jj new`
- `git commit --amend``jj describe` (working copy auto-amends)
- `git rebase -i``jj rebase`, `jj squash`, `jj edit`
- `git cherry-pick``jj rebase` or `jj duplicate`
- `git reflog``jj op log`
- `git reset --hard``jj undo` or `jj op restore`
- `git stash``jj new` (changes are always in commits)
- `git branch``jj bookmark`
## Troubleshooting
### Stale Working Copy
If working copy becomes stale (interrupted operations):
```bash
jj workspace update-stale
```
### Lost Changes
Check operation log and restore:
```bash
jj op log
jj op restore <operation-id>
```
### Unexpected State
View recent operations to understand what happened:
```bash
jj op log --limit 10
jj undo # Undo last operation
```
### Complex Conflicts
Use merge tools:
```bash
jj resolve --tool meld
# Or configure in settings
jj config set --user ui.merge-editor "code --wait --merge"
```
## Resources
### Supplemental Documentation
- **revsets.md** - Comprehensive revset language reference
- **filesets.md** - Complete fileset syntax and patterns
- **templating.md** - Template language for custom output
### Official Documentation
- Main docs: https://jj-vcs.github.io/jj/latest/
- Tutorial: https://jj-vcs.github.io/jj/latest/tutorial/
- GitHub: https://github.com/martinvonz/jj
## Guidelines for Assistance
When helping users with jujutsu:
1. **Understand their workflow** - Ask about their current VCS (Git, Mercurial, etc.)
2. **Explain key concepts** - Working copy as commit, change IDs, first-class conflicts
3. **Use revsets effectively** - Leverage the query language for complex operations
4. **Emphasize safety** - Highlight operation log and undo capabilities
5. **Show examples** - Provide concrete commands for their specific use case
6. **Reference supplemental docs** - Point to revsets.md, filesets.md, templating.md for details
7. **Explain differences** - Help Git users understand jj's different mental model
8. **Encourage experimentation** - Everything is recoverable via operation log
Remember: jujutsu's design philosophy prioritizes safety, ease of use, and powerful functionality. Help users leverage these strengths.

567
filesets.md Normal file
View file

@ -0,0 +1,567 @@
# Jujutsu Filesets - Complete Reference
Filesets are a functional language in jujutsu for selecting and filtering files. This document provides comprehensive coverage of the fileset syntax, operators, functions, and practical usage patterns.
## Overview
The fileset language (inspired by Mercurial) enables precise file selection across jujutsu commands. Expressions in this language are called "filesets" and can be used with any command that accepts file arguments.
## Core Concept
Filesets allow you to specify which files should be affected by operations using:
- File patterns and paths
- Logical operators for combining selections
- Functions for special selections
## File Pattern Types
### 1. Path Prefixes (Default)
Match files recursively under directories or exact file paths.
```bash
# Syntax: "path" or cwd:"path"
jj diff "src" # All files under src/
jj diff "src/main.rs" # Specific file
jj diff cwd:"relative" # Relative to current working directory
```
**Behavior:**
- If path is a directory: matches all files recursively under it
- If path is a file: matches that exact file
- Paths are relative to repository root by default
- `cwd:` prefix makes paths relative to current directory
### 2. Exact File Match
Match only specific files, not directories.
```bash
# Syntax: file:"path" or cwd-file:"path"
jj diff 'file:"src"' # Only if "src" is a file, not directory
jj diff 'cwd-file:"README.md"' # Exact file relative to cwd
```
**Use case:** When you want to ensure you're matching a file, not a directory.
### 3. Glob Patterns
Use Unix-style wildcards for pattern matching.
```bash
# Syntax: glob:"pattern"
jj diff 'glob:"*.rs"' # All Rust files in root
jj diff 'glob:"**/*.rs"' # All Rust files recursively
jj diff 'glob:"src/**/*.test.ts"' # All test files in src/
jj diff 'glob:"*.{rs,toml}"' # All .rs and .toml files
```
**Wildcard patterns:**
- `*` - Match any characters except `/`
- `**` - Match any characters including `/` (recursive)
- `?` - Match single character
- `[abc]` - Match one character from set
- `[a-z]` - Match one character from range
- `{a,b}` - Match either pattern a or b
### 4. Case-Insensitive Glob
Like glob but ignores case distinctions.
```bash
# Syntax: glob-i:"pattern"
jj diff 'glob-i:"*.MD"' # Matches .md, .MD, .Md, etc.
jj diff 'glob-i:"readme.*"' # Matches README.txt, readme.md, etc.
```
### 5. Root-Relative Variants
Explicitly specify paths relative to repository root.
```bash
# Syntax: root:, root-file:, root-glob:, root-glob-i:
jj diff 'root:src' # src/ from repo root
jj diff 'root-file:Cargo.toml' # Exact file from root
jj diff 'root-glob:**/test_*.rs' # Pattern from root
jj diff 'root-glob-i:**/README.*' # Case-insensitive from root
```
**Use case:** Ensure consistent behavior regardless of current working directory.
## Operators
Operators combine filesets using set logic. Listed by precedence (highest to lowest):
### 1. Negation (`~x`)
Select everything except x.
```bash
jj diff '~Cargo.lock' # Everything except Cargo.lock
jj diff '~glob:"*.test.ts"' # All files except tests
jj diff '~"target"' # Everything except target/
```
**Precedence:** Highest (binds tightly to operand)
### 2. Intersection (`x & y`)
Select files that match both x and y.
```bash
jj diff 'src & glob:"*.rs"' # Rust files in src/
jj diff 'glob:"*.rs" & ~glob:"*_test.rs"' # Rust files except tests
```
**Use case:** Narrow down selection by combining criteria.
### 3. Difference (`x ~ y`)
Select files in x but not in y.
```bash
jj diff 'src ~ glob:"*.test.ts"' # Files in src/ excluding tests
jj diff 'all() ~ "target"' # Everything except target/
```
**Use case:** Remove specific files from a larger selection.
### 4. Union (`x | y`)
Select files that match either x or y (or both).
```bash
jj diff 'glob:"*.rs" | glob:"*.toml"' # All Rust and TOML files
jj diff 'src | tests' # Files in src/ or tests/
jj diff '"README.md" | "LICENSE"' # Either file
```
**Precedence:** Lowest (evaluates last)
### Operator Precedence Summary
From highest to lowest:
1. `~x` (negation)
2. `x & y` (intersection)
3. `x ~ y` (difference)
4. `x | y` (union)
Use parentheses to override precedence:
```bash
jj diff '(src | tests) & glob:"*.rs"' # Rust files in src/ or tests/
jj diff 'src | (tests & glob:"*.rs")' # All src/ files OR Rust test files
```
## Functions
Built-in functions for special selections.
### 1. `all()`
Select all files in the working copy.
```bash
jj diff 'all()' # Show all changes
jj diff 'all() ~ "vendor"' # All files except vendor/
```
**Use case:** Starting point for exclusion-based selections.
### 2. `none()`
Select no files.
```bash
jj diff 'none()' # No output
jj diff 'none() | "README.md"' # Just README.md (contrived example)
```
**Use case:** Rare; mainly for testing or as a base for unions.
## Practical Examples
### Excluding Files
```bash
# Exclude single file
jj diff '~Cargo.lock'
# Exclude directory
jj diff '~target'
# Exclude multiple patterns
jj diff '~glob:"*.lock" & ~"target"'
```
### Working with Specific File Types
```bash
# All Rust files
jj diff 'glob:"**/*.rs"'
# Rust files except tests
jj diff 'glob:"**/*.rs" ~ glob:"**/*_test.rs"'
# TypeScript and JavaScript
jj diff 'glob:"**/*.{ts,js}"'
# Source files only (exclude tests and configs)
jj diff 'src & ~glob:"*.test.*" & ~glob:"*.config.*"'
```
### Directory-Based Selections
```bash
# Single directory
jj diff 'src'
# Multiple directories
jj diff 'src | tests | docs'
# Nested directories
jj diff 'src/components'
# Directory except subdirectory
jj diff 'src ~ src/generated'
```
### Complex Filtering
```bash
# Source Rust files, excluding tests and benchmarks
jj diff 'glob:"src/**/*.rs" ~ glob:"**/*_test.rs" ~ glob:"**/bench_*.rs"'
# Configuration files only
jj diff 'glob:"*.{toml,yaml,yml,json}"'
# Documentation files
jj diff 'glob:"**/*.md" | glob:"**/*.txt" | "LICENSE"'
# Modified files in src/ or tests/, excluding snapshots
jj diff '(src | tests) ~ glob:"**/*.snap"'
```
## Using Filesets in Commands
Filesets can be used with many jujutsu commands:
### `jj diff`
Show changes for selected files.
```bash
jj diff 'glob:"*.rs"' # Changes to Rust files
jj diff '~vendor' # Changes except vendor/
jj diff -r @- 'src' # Changes to src/ in parent commit
```
### `jj split`
Split commit including only selected files.
```bash
# Split out documentation changes
jj split 'glob:"**/*.md"'
# Split out everything except tests
jj split '~glob:"**/*.test.ts"'
# Split specific directory
jj split 'src/auth'
```
### `jj file list`
List files matching selection.
```bash
jj file list 'glob:"*.rs"' # List all Rust files
jj file list 'src ~ glob:"*.test.*"' # Source files excluding tests
```
### `jj restore`
Restore selected files from another commit.
```bash
jj restore --from @- 'Cargo.lock' # Restore lockfile from parent
jj restore 'glob:"*.config.ts"' # Restore config files
```
### `jj squash`
Squash only selected files.
```bash
# Squash only documentation changes
jj squash 'glob:"**/*.md"'
# Squash everything except tests
jj squash '~glob:"**/*.test.*"'
```
## Quoting and Shell Escaping
### When to Quote
Filesets often contain special characters that shells interpret. Always quote filesets:
```bash
# WRONG - Shell interprets * and other characters
jj diff glob:*.rs
# CORRECT - Quoted to protect from shell
jj diff 'glob:*.rs'
```
### Quote Style
Use single quotes to avoid shell variable expansion:
```bash
# Single quotes (preferred) - No expansion
jj diff '~glob:"*.lock"'
# Double quotes - Variables would expand
jj diff "~glob:\"*.lock\"" # More complex escaping needed
```
## Advanced Patterns
### Case-Insensitive Matching
```bash
# Match regardless of case
jj diff 'glob-i:"readme.*"' # README.md, readme.txt, Readme.rst
# Case-insensitive extension
jj diff 'glob-i:"*.jpg"' # .jpg, .JPG, .Jpg
```
### Combining Multiple Criteria
```bash
# TypeScript files in src/, excluding tests and generated files
jj diff '
src
& glob:"**/*.ts"
~ glob:"**/*.test.ts"
~ glob:"**/*.generated.ts"
'
# Any source file, excluding dependencies
jj diff '
(glob:"**/*.rs" | glob:"**/*.ts" | glob:"**/*.py")
~ "vendor"
~ "node_modules"
~ "target"
'
```
### Working Copy vs Repository Root
```bash
# From current directory
jj diff 'cwd:"utils"' # utils/ relative to pwd
# From repository root
jj diff 'root:utils' # utils/ relative to repo root
# Both styles in combination
jj diff 'cwd:"src" | root:tests'
```
## Common Patterns
### Exclude Generated Files
```bash
jj diff '
all()
~ "target"
~ "node_modules"
~ "dist"
~ "build"
~ glob:"*.generated.*"
'
```
### Source Code Only
```bash
jj diff '
glob:"**/*.{rs,ts,py,go,java}"
~ glob:"**/*.test.*"
~ glob:"**/*.spec.*"
'
```
### Configuration Files
```bash
jj diff 'glob:"*.{toml,yaml,yml,json,ini,conf}"'
```
### Documentation Files
```bash
jj diff '
glob:"**/*.md"
| glob:"**/*.rst"
| glob:"**/*.txt"
| "LICENSE"
| "README"
'
```
### Test Files Only
```bash
jj diff '
glob:"**/*.test.{ts,js}"
| glob:"**/*_test.{rs,go}"
| glob:"**/test_*.py"
'
```
## Best Practices
### 1. Start Simple, Then Refine
```bash
# Start broad
jj diff 'src'
# Add exclusions
jj diff 'src ~ glob:"*.test.*"'
# Further refine
jj diff 'src ~ glob:"*.test.*" ~ "src/generated"'
```
### 2. Use Functions for Clarity
```bash
# Clear intent with all()
jj diff 'all() ~ "target"'
# Rather than relying on implicit all
jj diff '~"target"' # Works but less explicit
```
### 3. Prefer Specific Patterns
```bash
# Specific - matches exactly what you want
jj diff 'glob:"src/**/*.rs"'
# Too broad - might match unexpected files
jj diff 'glob:"*.rs"'
```
### 4. Test Filesets with `jj file list`
```bash
# Test your fileset before using in operations
jj file list 'glob:"**/*.rs" ~ glob:"**/*_test.rs"'
# Then use in actual command
jj split 'glob:"**/*.rs" ~ glob:"**/*_test.rs"'
```
### 5. Use Root-Relative for Scripts
```bash
# In scripts, use root: for consistency
jj diff 'root:src/main.rs'
# Rather than cwd: which depends on pwd
jj diff 'cwd:src/main.rs' # Breaks if not in repo root
```
## Troubleshooting
### No Files Matched
If your fileset matches nothing:
```bash
# Check what files exist
jj file list
# Test individual parts
jj file list 'glob:"*.rs"'
jj file list 'src'
# Check if negation is too broad
jj file list '~glob:"*.test.*"' # Might exclude everything
```
### Unexpected Matches
```bash
# Use file list to debug
jj file list 'your-fileset-here'
# Check pattern specificity
jj file list 'glob:*.rs' # Only root level
jj file list 'glob:**/*.rs' # All levels (recursive)
```
### Shell Quoting Issues
```bash
# If seeing errors, check quoting
jj diff glob:*.rs # WRONG - shell expands *
jj diff 'glob:*.rs' # CORRECT - quoted
# For complex patterns, use single quotes
jj diff '~glob:"*.{rs,toml}"' # CORRECT
```
## Integration with Other Features
### With Revsets
Combine filesets with revset filtering:
```bash
# Show Rust file changes in specific commit
jj diff -r <commit> 'glob:"**/*.rs"'
# Changes to src/ between two commits
jj diff --from <commit1> --to <commit2> 'src'
```
### With Interactive Commands
```bash
# Interactively squash only test files
jj squash -i 'glob:"**/*.test.*"'
# Interactively split documentation changes
jj split 'glob:"**/*.md"'
```
### In Configuration
Some commands accept default filesets in config:
```toml
[ui]
# Example: ignore certain files in diff by default
# (Check current jj docs for actual config options)
```
## Summary
Filesets provide powerful, flexible file selection in jujutsu:
- **Pattern types**: paths, files, globs (case-sensitive and insensitive), root-relative
- **Operators**: `~` (not), `&` (and), `~` (minus), `|` (or)
- **Functions**: `all()`, `none()`
- **Always quote** filesets to protect from shell interpretation
- **Test with** `jj file list` before using in destructive operations
- **Combine with revsets** for precise change management
For more information, see the official jujutsu documentation on filesets.

818
revsets.md Normal file
View file

@ -0,0 +1,818 @@
# 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.
```bash
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.
```bash
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.
```bash
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.
```bash
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).
```bash
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.
```bash
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):
```bash
jj log -r @-+ # Children of all parents (siblings)
```
#### `x+` (Children)
Immediate children of x.
```bash
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.
```bash
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.
```bash
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.
```bash
jj log -r main::@ # Commits from main to @
jj log -r @-::@ # Just @ (parent to working copy)
```
#### `::x` with depth
Ancestors with limited depth.
```bash
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.
```bash
jj log -r ..@ # Ancestors of @ without root
jj log -r ..main # History up to main
```
#### `x..` (Non-ancestors)
x and its descendants, excluding ancestors.
```bash
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).
```bash
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).
```bash
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.
```bash
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.
```bash
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.
```bash
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:
```bash
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.
```bash
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.
```bash
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.
```bash
jj log -r 'connected(@)' # Connected component containing @
```
#### `reachable(x, domain)`
Commits in domain that are reachable from x.
```bash
jj log -r 'reachable(@, all())' # All reachable from @
```
### Set Functions
#### `all()`
All commits in the repository.
```bash
jj log -r 'all()' # Entire history
jj log -r 'all() ~ ::main' # Everything not in main's history
```
#### `none()`
Empty set (no commits).
```bash
jj log -r 'none()' # No output
jj log -r 'none() | @' # Just @ (contrived example)
```
#### `heads(x)`
Commits in x that have no children in x.
```bash
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.
```bash
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.
```bash
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.
```bash
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.
```bash
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.
```bash
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.
```bash
jj log -r 'git_refs()' # All Git refs
```
#### `git_head()`
Git's HEAD reference.
```bash
jj log -r 'git_head()' # Show Git HEAD
```
#### `tracked_remote_bookmarks([bookmark_pattern[, [remote=]remote_pattern]])`
Bookmarks with remote tracking configured.
```bash
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.
```bash
jj log -r 'untracked_remote_bookmarks()' # Untracked remotes
```
### Author and Committer Functions
#### `author(pattern)`
Commits where author matches pattern.
```bash
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.
```bash
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.
```bash
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.
```bash
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.
```bash
jj log -r 'subject(feat:)' # Feature commits
jj log -r 'subject(glob:*WIP*)' # WIP commits
```
### File Functions
#### `files(pattern)`
Commits modifying files matching pattern.
```bash
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.
```bash
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).
```bash
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.
```bash
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).
```bash
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.
```bash
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.
```bash
jj log -r 'visible_heads()' # All visible heads
```
#### `immutable_heads()`
Heads of immutable commits.
```bash
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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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`:
```toml
[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:
```bash
jj log -r work
jj log -r attention
jj log -r recent
```
### 4. Use `present()` for Robustness
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
# 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
```bash
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
```bash
jj edit <revset> # Edit commit
jj squash -r <revset> # Squash commit
jj abandon <revset> # Abandon commits
jj describe -r <revset> # Set description
```
### Rebasing
```bash
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
```bash
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.

879
templating.md Normal file
View file

@ -0,0 +1,879 @@
# Jujutsu Templates - Complete Reference
Templates are a functional language in jujutsu for customizing command output. This document provides comprehensive coverage of template syntax, keywords, functions, operators, and practical usage patterns.
## Overview
The template language enables customization of jujutsu command output via the `-T`/`--template` option. Templates can format text, apply styling, perform calculations, and implement conditional logic.
## Basic Usage
### Command Line Templates
```bash
# Simple template
jj log -T 'commit_id.short()'
# Multiple components
jj log -T 'commit_id.short() ++ " " ++ description.first_line()'
# With formatting
jj log -T 'commit_id.short() ++ "\n" ++ indent(" ", description)'
```
### Configuration Templates
Set default templates in config (`jj config edit --user`):
```toml
[templates]
log = '''
commit_id.short() ++ " "
++ if(conflict(), "⚠️ ", "")
++ description.first_line()
'''
op_log = 'id.short() ++ " " ++ description'
```
## Keywords
Keywords represent objects and their properties. The available keywords depend on the command context.
### Commit Keywords
Available in commit templates (e.g., `jj log`, `jj show`):
#### Basic Properties
- `commit_id` - Commit ID (CommitId type)
- `change_id` - Change ID (ChangeId type)
- `description` - Commit description (String)
- `author` - Commit author (Signature type)
- `committer` - Committer (Signature type)
- `working_copies` - Workspaces where this is the working copy (String)
- `current_working_copy` - Boolean, true if this is current working copy
- `bookmarks` - Bookmarks pointing to this commit (List<RefName>)
- `tags` - Tags pointing to this commit (List<RefName>)
- `git_refs` - Git refs pointing to this commit (List<RefName>)
- `git_head` - Git HEAD if pointing to this commit (RefName)
- `divergent` - Boolean, true if change has divergent commits
- `hidden` - Boolean, true if commit is hidden
- `immutable` - Boolean, true if commit is immutable
- `conflict` - Boolean, true if commit has conflicts
- `empty` - Boolean, true if commit has no changes
- `root` - Boolean, true if this is the root commit
#### Relationship Properties
- `parents` - Parent commits (List<Commit>)
- `contained_in` - Bookmarks/tags containing this commit (String)
#### Tree Properties
- `tree` - Commit tree object (Tree type)
### Operation Keywords
Available in operation templates (e.g., `jj op log`):
- `current_operation` - Boolean, true for current operation
- `description` - Operation description (String)
- `id` - Operation ID (OperationId type)
- `tags` - Tags on this operation (String)
- `time` - Operation timestamp (Timestamp type)
- `user` - User who performed operation (String)
- `root` - Boolean, true if root operation
## Types and Methods
### CommitId / ChangeId
Hexadecimal identifiers.
**Methods:**
- `.short([len])` - Abbreviated form (default 12 chars)
- `.shortest([min_len])` - Shortest unique prefix (min 4 chars)
- `.hex()` - Full hexadecimal string
```bash
jj log -T 'commit_id.short()' # 12 chars
jj log -T 'commit_id.short(8)' # 8 chars
jj log -T 'commit_id.shortest()' # Unique prefix
jj log -T 'change_id.hex()' # Full ID
```
### String
Text values.
**Methods:**
- `.contains(needle)` - Check if contains substring
- `.starts_with(prefix)` - Check if starts with prefix
- `.ends_with(suffix)` - Check if ends with suffix
- `.remove_prefix(prefix)` - Remove prefix if present
- `.remove_suffix(suffix)` - Remove suffix if present
- `.substr(start, end)` - Extract substring
- `.first_line()` - First line only
- `.lines()` - Split into list of lines
- `.upper()` - Convert to uppercase
- `.lower()` - Convert to lowercase
- `.len()` - String length
```bash
jj log -T 'description.first_line()'
jj log -T 'description.upper()'
jj log -T 'if(description.contains("bug"), "🐛", "")'
jj log -T 'description.substr(0, 50)'
```
### Signature
Author/committer information.
**Methods:**
- `.name()` - Person's name
- `.email()` - Email address
- `.username()` - Username (part before @ in email)
- `.timestamp()` - Signature timestamp
```bash
jj log -T 'author.name()'
jj log -T 'author.email()'
jj log -T 'author.username() ++ " " ++ author.timestamp()'
```
### Timestamp
Date and time values.
**Methods:**
- `.ago()` - Relative time ("2 days ago")
- `.format(format_string)` - Custom format (strftime)
- `.utc()` - Format in UTC
```bash
jj log -T 'author.timestamp().ago()'
jj log -T 'author.timestamp().format("%Y-%m-%d")'
jj log -T 'author.timestamp().format("%Y-%m-%d %H:%M:%S")'
jj log -T 'author.timestamp().utc()'
```
Format specifiers (strftime):
- `%Y` - Year (4 digits)
- `%m` - Month (01-12)
- `%d` - Day (01-31)
- `%H` - Hour (00-23)
- `%M` - Minute (00-59)
- `%S` - Second (00-59)
- `%a` - Weekday abbreviation
- `%b` - Month abbreviation
### Boolean
True/false values.
**Usage in conditionals:**
```bash
jj log -T 'if(conflict, "⚠️ CONFLICT", "")'
jj log -T 'if(empty, "[empty]", description.first_line())'
jj log -T 'if(current_working_copy, "→ ", " ")'
```
### List
Collections of values.
**Methods:**
- `.len()` - Number of items
- `.join(separator)` - Join into string
- `.map(|item| template)` - Transform each item
- `.filter(|item| condition)` - Select items matching condition
- `.any(|item| condition)` - True if any item matches
- `.all(|item| condition)` - True if all items match
```bash
# Join parent IDs
jj log -T 'parents.map(|c| c.commit_id().short()).join(", ")'
# Show bookmarks
jj log -T 'bookmarks.map(|b| b.name()).join(" ")'
# Filter and show
jj log -T 'tags.filter(|t| t.name().starts_with("v")).map(|t| t.name()).join(", ")'
```
### RefName
Reference names (bookmarks, tags, git refs).
**Methods:**
- `.name()` - Reference name
- `.remote()` - Remote name (for remote refs)
- `.present()` - Boolean, true if ref exists
```bash
jj log -T 'bookmarks.map(|b| b.name()).join(", ")'
jj log -T 'git_refs.map(|r| r.name()).join(", ")'
```
### Commit
Full commit objects (from parents, etc.).
**Methods:**
All commit keywords available as methods:
- `.commit_id()`
- `.change_id()`
- `.description()`
- `.author()`
- `.parents()`
- etc.
```bash
jj log -T 'parents.map(|c| c.commit_id().short()).join(", ")'
jj log -T 'parents.map(|c| c.author().name()).join(" & ")'
```
## Operators
### Template Concatenation (`++`)
Combine templates.
```bash
jj log -T 'commit_id.short() ++ " " ++ description.first_line()'
jj log -T 'author.name() ++ " <" ++ author.email() ++ ">"'
jj log -T '"[" ++ change_id.short() ++ "]"'
```
### Logical Operators
- `x && y` - Logical AND
- `x || y` - Logical OR
- `!x` - Logical NOT
```bash
jj log -T 'if(conflict && !empty, "⚠️", "")'
jj log -T 'if(immutable || hidden, "SKIP", commit_id.short())'
```
### Comparison Operators
- `x == y` - Equal
- `x != y` - Not equal
- `x < y` - Less than
- `x > y` - Greater than
- `x <= y` - Less than or equal
- `x >= y` - Greater than or equal
```bash
jj log -T 'if(parents.len() > 1, "MERGE", "NORMAL")'
jj log -T 'if(description.len() < 10, "SHORT", description.first_line())'
```
### Arithmetic Operators
- `x + y` - Addition
- `x - y` - Subtraction
- `x * y` - Multiplication
- `x / y` - Division (integer)
- `x % y` - Modulo
```bash
jj log -T 'if(author.timestamp().format("%H").parse_int() > 12, "PM", "AM")'
```
### Method Calls
- `x.method()` - Call method on object
- `x.method(arg1, arg2)` - Call with arguments
```bash
jj log -T 'commit_id.short(8)'
jj log -T 'description.substr(0, 50)'
jj log -T 'author.timestamp().format("%Y-%m-%d")'
```
## Functions
Built-in template functions.
### Conditional Functions
#### `if(condition, then, else)`
Conditional output.
```bash
jj log -T 'if(conflict, "⚠️ ", "") ++ description.first_line()'
jj log -T 'if(empty, "[empty]", commit_id.short())'
jj log -T 'if(current_working_copy, "→ ", " ") ++ description.first_line()'
# Nested conditionals
jj log -T '
if(conflict, "⚠️",
if(empty, "∅",
if(divergent, "◆", "●")))
++ " " ++ description.first_line()
'
```
#### `coalesce(option1, option2, ...)`
Return first non-empty value.
```bash
jj log -T 'coalesce(description, "[no description]")'
jj log -T 'coalesce(bookmarks.map(|b| b.name()).join(", "), "[no bookmarks]")'
```
### Formatting Functions
#### `label(label, content)`
Apply styling/color labels.
```bash
jj log -T 'label("commit_id", commit_id.short())'
jj log -T 'label("error", "CONFLICT") ++ " " ++ description'
jj log -T 'if(conflict, label("error", "⚠"), "") ++ description.first_line()'
```
Common labels:
- `commit_id` - Commit ID styling
- `change_id` - Change ID styling
- `author` - Author name styling
- `timestamp` - Timestamp styling
- `bookmark` - Bookmark styling
- `tag` - Tag styling
- `error` - Error styling
- `warning` - Warning styling
#### `fill(width, content)`
Wrap text to specified width.
```bash
jj log -T 'fill(80, description)'
jj log -T 'commit_id.short() ++ "\n" ++ fill(72, description)'
```
#### `indent(prefix, content)`
Indent lines with prefix.
```bash
jj log -T 'commit_id.short() ++ "\n" ++ indent(" ", description)'
jj log -T 'description.first_line() ++ "\n" ++ indent(" ", author.name())'
```
#### `pad_start(width, content[, fill_char])`
Pad start to width.
```bash
jj log -T 'pad_start(10, commit_id.short())'
jj log -T 'pad_start(20, author.name(), ".")'
```
#### `pad_end(width, content[, fill_char])`
Pad end to width.
```bash
jj log -T 'pad_end(50, description.first_line())'
jj log -T 'pad_end(30, author.name(), " ") ++ commit_id.short()'
```
#### `truncate_start(width, content)`
Truncate from start if too long.
```bash
jj log -T 'truncate_start(50, description.first_line())'
```
#### `truncate_end(width, content)`
Truncate from end if too long.
```bash
jj log -T 'truncate_end(50, description.first_line())'
jj log -T 'pad_end(60, truncate_end(60, description.first_line()))'
```
### List Functions
#### `separate(separator, ...values)`
Join values with separator, skipping empty ones.
```bash
jj log -T 'separate(" | ", commit_id.short(), author.name(), description.first_line())'
jj log -T 'separate("\n", description, bookmarks.map(|b| b.name()).join(", "))'
```
Unlike `++`, `separate()` skips empty values:
```bash
# If bookmarks is empty, no trailing separator
jj log -T 'separate(" ", commit_id.short(), bookmarks.map(|b| b.name()).join(","))'
```
### Serialization Functions
#### `json(value)`
Serialize value to JSON.
```bash
jj log -T 'json(commit_id.short())'
jj log -T 'json(description.first_line())'
# For structured output
jj log -T '
"{" ++
"\"commit\": " ++ json(commit_id.short()) ++ "," ++
"\"description\": " ++ json(description.first_line()) ++
"}"
'
```
## Practical Examples
### Compact Log Format
```bash
jj log -T '
commit_id.short()
++ " "
++ author.username()
++ " "
++ author.timestamp().ago()
++ " "
++ description.first_line()
'
```
### Detailed Format
```bash
jj log -T '
"commit " ++ commit_id.short() ++ "\n"
++ "Author: " ++ author.name() ++ " <" ++ author.email() ++ ">\n"
++ "Date: " ++ author.timestamp().format("%Y-%m-%d %H:%M:%S") ++ "\n"
++ "\n"
++ indent(" ", description)
'
```
### Status Indicators
```bash
jj log -T '
if(current_working_copy, "→", " ")
++ if(conflict, "⚠", " ")
++ if(empty, "∅", " ")
++ if(divergent, "◆", " ")
++ " "
++ commit_id.short()
++ " "
++ description.first_line()
'
```
### Branch and Tag Display
```bash
jj log -T '
commit_id.short()
++ " "
++ separate(" ",
bookmarks.map(|b| label("bookmark", b.name())).join(" "),
tags.map(|t| label("tag", t.name())).join(" "))
++ if(bookmarks.len() > 0 || tags.len() > 0, "\n ", " ")
++ description.first_line()
'
```
### Parent Commit IDs
```bash
jj log -T '
commit_id.short()
++ " (parents: "
++ parents.map(|c| c.commit_id().short()).join(", ")
++ ") "
++ description.first_line()
'
```
### Merge Commit Detection
```bash
jj log -T '
if(parents.len() > 1,
"MERGE " ++ commit_id.short() ++ "\n"
++ " parents: " ++ parents.map(|c| c.commit_id().short()).join(", ") ++ "\n"
++ " " ++ description.first_line(),
commit_id.short() ++ " " ++ description.first_line())
'
```
### JSON Output
```bash
jj log -T '
"{\"commit_id\": " ++ json(commit_id.short())
++ ", \"change_id\": " ++ json(change_id.short())
++ ", \"author\": " ++ json(author.name())
++ ", \"email\": " ++ json(author.email())
++ ", \"date\": " ++ json(author.timestamp().format("%Y-%m-%d %H:%M:%S"))
++ ", \"description\": " ++ json(description.first_line())
++ "}"
'
```
### Author Statistics
```bash
jj log -T '
author.name()
++ " ("
++ author.email()
++ ") - "
++ author.timestamp().format("%Y-%m-%d")
++ " - "
++ commit_id.short()
'
```
### Time-Based Formatting
```bash
jj log -T '
"["
++ author.timestamp().format("%Y-%m-%d %H:%M")
++ "] "
++ author.username()
++ ": "
++ truncate_end(60, description.first_line())
'
```
## Template Aliases
Define reusable template fragments in config:
```toml
[template-aliases]
# Simple substitutions
short_commit = 'commit_id.short()'
full_author = 'author.name() ++ " <" ++ author.email() ++ ">"'
# Complex templates
format_timestamp = 'author.timestamp().format("%Y-%m-%d %H:%M")'
status_icons = '''
if(current_working_copy, "→", " ")
++ if(conflict, "⚠", " ")
++ if(empty, "∅", " ")
'''
compact_commit = '''
commit_id.short()
++ " "
++ author.username()
++ " "
++ description.first_line()
'''
```
Use in templates:
```bash
jj log -T 'status_icons ++ compact_commit'
jj log -T 'short_commit ++ " by " ++ full_author'
```
## Advanced Patterns
### Conditional Styling
```bash
jj log -T '
label(
if(conflict, "error",
if(empty, "warning", "commit_id")),
commit_id.short())
++ " "
++ description.first_line()
'
```
### Multi-Line Descriptions
```bash
jj log -T '
commit_id.short() ++ "\n"
++ separate("\n",
if(bookmarks.len() > 0, "Bookmarks: " ++ bookmarks.map(|b| b.name()).join(", "), ""),
if(tags.len() > 0, "Tags: " ++ tags.map(|t| t.name()).join(", "), ""))
++ "\n"
++ indent(" ", description)
++ "\n"
'
```
### Working Copy Highlighting
```bash
jj log -T '
if(current_working_copy,
label("working_copy", ">> " ++ commit_id.short() ++ " <<"),
" " ++ commit_id.short() ++ " ")
++ " "
++ description.first_line()
'
```
### List Filtering and Mapping
```bash
# Show only remote bookmarks
jj log -T '
commit_id.short()
++ " "
++ bookmarks
.filter(|b| b.remote() != "")
.map(|b| b.remote() ++ "/" ++ b.name())
.join(", ")
'
# Show version tags only
jj log -T '
commit_id.short()
++ " "
++ tags
.filter(|t| t.name().starts_with("v"))
.map(|t| t.name())
.join(", ")
'
```
### Complex Conditional Logic
```bash
jj log -T '
if(conflict,
label("error", "⚠ CONFLICT") ++ " " ++ commit_id.short(),
if(empty,
label("warning", "∅ EMPTY") ++ " " ++ commit_id.short(),
if(divergent,
label("warning", "◆ DIVERGENT") ++ " " ++ commit_id.short(),
commit_id.short())))
++ "\n"
++ if(current_working_copy, "→ ", " ")
++ description.first_line()
'
```
## Performance Considerations
### Avoid Expensive Operations in Large Histories
```bash
# Expensive - computes for every commit
jj log -T 'commit_id.short() ++ " " ++ contained_in'
# Better - only show what's needed
jj log -r 'latest(all(), 50)' -T 'commit_id.short() ++ " " ++ description.first_line()'
```
### Limit Template Complexity
```bash
# Complex template - may be slow
jj log -T '
commit_id.short()
++ " "
++ parents.map(|c|
c.commit_id().short()
++ " by "
++ c.author().name()
).join(", ")
++ " "
++ description
'
# Simpler alternative
jj log -T '
commit_id.short()
++ " (parents: " ++ parents.map(|c| c.commit_id().short()).join(", ") ++ ")"
++ " "
++ description.first_line()
'
```
## Best Practices
### 1. Build Templates Incrementally
```bash
# Start simple
jj log -T 'commit_id.short()'
# Add author
jj log -T 'commit_id.short() ++ " " ++ author.name()'
# Add description
jj log -T 'commit_id.short() ++ " " ++ author.name() ++ " " ++ description.first_line()'
# Add formatting
jj log -T '
commit_id.short()
++ " "
++ truncate_end(20, author.name())
++ " "
++ description.first_line()
'
```
### 2. Use Template Aliases
Define commonly-used patterns once:
```toml
[template-aliases]
my_log = '''
if(current_working_copy, "→ ", " ")
++ commit_id.short()
++ " "
++ author.username()
++ " "
++ author.timestamp().ago()
++ " "
++ description.first_line()
'''
```
```bash
jj log -T my_log
```
### 3. Test Templates on Small Sets
```bash
# Test on single commit
jj log -r @ -T 'your template here'
# Test on small range
jj log -r 'latest(all(), 5)' -T 'your template here'
```
### 4. Use Proper Quoting
```bash
# Single quotes for templates (avoid shell expansion)
jj log -T 'commit_id.short() ++ " " ++ description'
# Escape quotes inside template strings
jj log -T '"[" ++ commit_id.short() ++ "]"'
```
### 5. Leverage `separate()` for Clean Output
```bash
# Rather than manual checking for empty values
jj log -T 'commit_id.short() ++ if(bookmarks.len() > 0, " " ++ bookmarks.map(|b| b.name()).join(","), "")'
# Use separate
jj log -T 'separate(" ", commit_id.short(), bookmarks.map(|b| b.name()).join(","))'
```
## Troubleshooting
### Template Syntax Errors
```bash
# Error: missing closing quote
jj log -T 'commit_id.short()
# Fix: close the quote
jj log -T 'commit_id.short()'
# Error: mismatched parentheses
jj log -T 'if(conflict, "X"'
# Fix: close all parentheses
jj log -T 'if(conflict, "X", "")'
```
### Type Errors
```bash
# Error: trying to call string method on commit
jj log -T 'parents.short()'
# Fix: map over list and call method on each
jj log -T 'parents.map(|c| c.commit_id().short()).join(", ")'
# Error: treating string as boolean
jj log -T 'if(description, "yes", "no")'
# Fix: check for empty string
jj log -T 'if(description != "", "yes", "no")'
```
### Empty Output
```bash
# Check if keywords exist
jj log -r @ -T 'description'
# Verify list length
jj log -r @ -T 'bookmarks.len()'
# Use coalesce for defaults
jj log -T 'coalesce(bookmarks.map(|b| b.name()).join(","), "[no bookmarks]")'
```
## Integration with Commands
### Log Output
```bash
jj log -T 'template'
jj log -r 'revset' -T 'template'
jj log --no-graph -T 'template' # Without graph
```
### Show Command
```bash
jj show -T 'template'
```
### Operation Log
```bash
jj op log -T 'template'
```
### Scriptable Output
```bash
# Get commit IDs for scripting
jj log -r 'mine()' -T 'commit_id.hex()' --no-graph
# JSON output
jj log -r @ -T '{"id": ' ++ json(commit_id.short()) ++ '}' --no-graph
```
## Summary
Templates provide powerful output customization in jujutsu:
- **Keywords**: `commit_id`, `change_id`, `description`, `author`, `committer`, etc.
- **Types**: CommitId, String, Signature, Timestamp, Boolean, List, RefName, Commit
- **Operators**: `++` (concat), `&&/||/!` (logical), `==/!=/</>` (comparison), `+/-/*` (arithmetic)
- **Functions**: `if()`, `coalesce()`, `label()`, `fill()`, `indent()`, `separate()`, etc.
- **Methods**: `.short()`, `.first_line()`, `.contains()`, `.map()`, `.filter()`, etc.
- **Aliases**: Define reusable templates in config
- **Best practices**: Build incrementally, use aliases, test on small sets
For commit selection, see revsets.md. For file filtering, see filesets.md.
Master templates to create custom, informative output that fits your workflow perfectly.