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

12 KiB

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.

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

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

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

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

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

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.

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.

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

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:

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.

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.

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

# 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

# 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

# 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

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

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.

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

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.

jj restore --from @- 'Cargo.lock'  # Restore lockfile from parent
jj restore 'glob:"*.config.ts"'     # Restore config files

jj squash

Squash only selected files.

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

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

# 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

# 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

# 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

# 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

jj diff '
  all()
  ~ "target"
  ~ "node_modules"
  ~ "dist"
  ~ "build"
  ~ glob:"*.generated.*"
'

Source Code Only

jj diff '
  glob:"**/*.{rs,ts,py,go,java}"
  ~ glob:"**/*.test.*"
  ~ glob:"**/*.spec.*"
'

Configuration Files

jj diff 'glob:"*.{toml,yaml,yml,json,ini,conf}"'

Documentation Files

jj diff '
  glob:"**/*.md"
  | glob:"**/*.rst"
  | glob:"**/*.txt"
  | "LICENSE"
  | "README"
'

Test Files Only

jj diff '
  glob:"**/*.test.{ts,js}"
  | glob:"**/*_test.{rs,go}"
  | glob:"**/test_*.py"
'

Best Practices

1. Start Simple, Then Refine

# 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

# 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

# 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

# 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

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

# 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

# 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

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

# 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

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

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