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

567 lines
12 KiB
Markdown

# 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.