From c4fa6e07cb4eb67b52b3b9c8360bb4ae93170fb3 Mon Sep 17 00:00:00 2001 From: sloane Date: Mon, 3 Nov 2025 14:13:34 -0500 Subject: [PATCH] initial commit --- SKILL.md | 368 +++++++++++++++++++++ filesets.md | 567 ++++++++++++++++++++++++++++++++ revsets.md | 818 ++++++++++++++++++++++++++++++++++++++++++++++ templating.md | 879 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 2632 insertions(+) create mode 100644 SKILL.md create mode 100644 filesets.md create mode 100644 revsets.md create mode 100644 templating.md diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..71cb0fa --- /dev/null +++ b/SKILL.md @@ -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 # 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 '' # Show filtered commits +jj op log # Show operation history +jj show # Show commit details +jj diff # Show working copy changes +jj diff -r # 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 # Create new commit on specified base +jj edit # Edit an existing commit +jj abandon # Abandon a commit (preserve children) +``` + +### Moving Changes +```bash +jj squash # Move changes from @ into parent +jj squash -r # Squash specific commit into parent +jj squash -i # Interactively select changes to squash +jj split # Split current commit into multiple +jj move --from --to # Move changes between commits +jj diffedit # Interactively edit changes +``` + +### Branching and Bookmarks +```bash +jj bookmark create # Create bookmark at current commit +jj bookmark set -r # Set bookmark to specific commit +jj bookmark list # List all bookmarks +jj bookmark delete # Delete a bookmark +jj bookmark track @ # Track remote bookmark +``` + +### Rebasing +```bash +jj rebase -d # Rebase current commit +jj rebase -r -d # Rebase specific commit +jj rebase -s -d # Rebase source and descendants +jj rebase -b -d # Rebase branch (all ancestors) +``` + +### Conflict Resolution +```bash +jj resolve # Interactively resolve conflicts +jj resolve --list # List conflicted files +jj resolve # Resolve specific file +``` + +### Working with Git +```bash +jj git fetch # Fetch from remotes +jj git push # Push changes +jj git push --change # Push specific change +jj git push --bookmark # Push specific bookmark +jj git remote add # 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 # Undo specific operation +jj op restore # Restore to specific operation +jj op abandon # Abandon operation from log +``` + +### Multi-Workspace +```bash +jj workspace add # Create new workspace +jj workspace list # List workspaces +jj workspace forget # Remove workspace +jj workspace update-stale # Update stale working copy +``` + +## Important Symbols and Operators + +### Revset Symbols +- `@` - The working copy commit +- `@` - Working copy in another workspace +- `@` - 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 # 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 # Edit the commit +jj split # Interactively split changes + +# Edit changes visually +jj diffedit -r # 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 # 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 + +# Abandon unwanted changes +jj abandon +``` + +## 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 +``` + +### 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. diff --git a/filesets.md b/filesets.md new file mode 100644 index 0000000..2af4b54 --- /dev/null +++ b/filesets.md @@ -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 'glob:"**/*.rs"' + +# Changes to src/ between two commits +jj diff --from --to '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. diff --git a/revsets.md b/revsets.md new file mode 100644 index 0000000..7004d7b --- /dev/null +++ b/revsets.md @@ -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 @ +``` + +#### `@` +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 + +#### `@` +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 '' # Show log +jj show -r '' # Show details +jj diff -r '' # Show changes +jj evolog -r '' # Show evolution log +``` + +### Modifying Commits + +```bash +jj edit # Edit commit +jj squash -r # Squash commit +jj abandon # Abandon commits +jj describe -r # Set description +``` + +### Rebasing + +```bash +jj rebase -d # Rebase destination +jj rebase -r -d # Rebase specific commit +jj rebase -s -d # Rebase source and descendants +``` + +### Branching + +```bash +jj bookmark set -r # Set bookmark +jj new # 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. diff --git a/templating.md b/templating.md new file mode 100644 index 0000000..b928a98 --- /dev/null +++ b/templating.md @@ -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) +- `tags` - Tags pointing to this commit (List) +- `git_refs` - Git refs pointing to this commit (List) +- `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) +- `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.