Fileset & File-Selection Query Languages
A primary-source survey of the languages people use to name a set of files — the infix set algebras of Jujutsu and Mercurial, the ordered rule lists of .gitignore and rsync, the prefix namespace of git pathspec, the combinator API of nixpkgs' lib.fileset, and the implicit-conjunction prompts of fzf and fff. The evidence base for the textual query DSL that sparkles:build-primitives is being redesigned around, and for hue's picker query language (PKQ*).
This survey answers seven questions:
- Which composition model does a fileset language need? Set algebra — because an ordered rule list provably cannot express intersection, and all four ordered-rule subjects have accreted a workaround for the one place they needed it. See concepts and the comparison.
- What precedence?
&and difference above|, left-associative — the independent conclusion of jj and Mercurial, withbazel query's flat table as the counter-example and fzf's inverted one as the consequence of implicit conjunction. - How is a non-path selector reached? Through a
kind:prefix, unanimously — with git pathspec's:(word,word)long form as the only shape whose modifiers compose additively. - Anchored or floating? Both, and the field splits by context: Mercurial ships an anchored query language and a floating ignore file in the same binary and documents why. Answered for our case in the recommendations.
- Can the language afford a unary complement? Only if its universe is already enumerated. Everything in this survey that walks a tree either omits complement or accepts that an excluded directory is never opened.
- What does it cost to skip the language and classify tokens by heuristic?fff's parser, which is the most honest available answer.
- What does an unprunable language cost? More than speed: git sparse-checkout found that its general pattern mode made
--sparse-index"likely impossible", deprecated it, and replaced it with a canonical directory set. The typed counterpart of the prefix lattice —Everything/Nothing/ShouldTraverse— is Sapling's, and the split between what enumerates and what filters is watchman's.
Last reviewed: September 20, 2026
NOTE
Scope. Twenty-two systems, surveyed across fourteen deep-dives. Deferred, noted not omitted: Buck2 and Pants glob() (both follow Bazel's include/exclude shape), Perforce client-view mappings, MSBuild's Include/Exclude/Remove triple, and .dockerignore (a .gitignore-shaped dialect with divergences of its own). Out of scope: content search languages — regexes, trigram query planners and ranked retrieval are surveyed in Full-Text Search; the matching and ranking of a fuzzy query is surveyed in Fuzzy Matching. This tree is only about the set-naming layer.
Every GitHub citation pins a 40-character commit SHA. Mercurial's sources are cited to a tag on a host that does not answer automated checkers; each quote is reproducible offline with nix shell nixpkgs#mercurial -c hg help filesets.
Master catalog
| Subject | Ecosystem | Category | Composition | Operators | Complement | Link |
|---|---|---|---|---|---|---|
| jj filesets | Rust | VCS query language | set algebra | | & ~ + prefix ~ | yes | jj |
| jj revsets | Rust | VCS query language | set algebra + graph | | & ~ + 4 graph levels | yes | revsets |
| Mercurial filesets | Python | VCS query language | set algebra | | & - + and or and not ! | yes | hg |
Sapling pathmatcher | Rust | VCS query language | set algebra | API combinators | no | sapling |
| watchman | C++ / JSON | File-watching query | generate then filter | allof/anyof/not | yes | watchman |
bazel query | Java | Build-graph query | set algebra | ^ + - / intersect union except | no | bazel |
Bazel glob() | Starlark | Build-file file selection | ordered rules | none | no | bazel |
| git pathspec | C | CLI path limiter | ordered rules | :! / :^ | no | pathspec |
| git sparse-checkout | C | Working-tree filter | ordered rules → directory set | ! (non-cone only) | per-rule | sparse |
.gitignore | C | Ordered rule file | ordered rules | ! | per-rule | ordered |
| rsync filter rules | C | Ordered rule file | ordered rules | + / - per line | per-rule | ordered |
.npmignore / files | JS | Ordered rule file | ordered rules | !, polarity inverted | per-rule | ordered |
| CODEOWNERS | — | Ordered rule file | ordered rules | none (! removed) | no | ordered |
the ignore crate | Rust | Walker + matcher library | ordered rules | ! in globs | per-rule | ignore |
Ant <fileset> | Java | Build-file selection | two-layer hybrid | <and>/<or>/<not>/<none> | yes | ant |
Gradle PatternSet | Java | Build-file selection | two-layer hybrid | intersect/union/negate | yes | ant |
nixpkgs lib.fileset | Nix | Build-input selection | set algebra | functions | no | nix |
| fzf | Go | Interactive finder | implicit conjunction | sigils + | | per-term | implicit |
| fff | Rust | Interactive finder | implicit conjunction | ! prefix | per-term | implicit |
GNU find | C | Tree traversal | explicit + implicit AND | ! -a -o , + ( ) | yes | shell |
fd | Rust | Tree traversal | ordered rules | ! in globs, --and | per-glob | shell |
| ripgrep | Rust | Content search filter | ordered rules | ! in globs | per-glob | shell |
Taxonomy
By composition model
| Model | Subjects | Can intersect? |
|---|---|---|
| Set algebra | jj, jj revsets, Mercurial, Bazel query, nixpkgs, Sapling | yes |
| Ordered rules | .gitignore, rsync, npm, CODEOWNERS, Bazel glob(), git pathspec, sparse-checkout, the ignore crate, fd, ripgrep | no |
| Generate then filter | watchman | within a generator |
| Two-layer hybrid | Ant, Gradle | in the upper layer |
| Implicit conjunction | fzf, fff, find | one axis only |
By evaluation universe
| Universe | Subjects | Complement affordable? |
|---|---|---|
| Materialised manifest or index | jj, revsets, Mercurial, Bazel query, fff | yes |
| A self-maintained index | watchman | within a generator's bound |
| A tree that must be walked | .gitignore, pathspec, sparse-checkout, nixpkgs, fd/rg, the ignore crate, glob(), Ant/Gradle, Sapling | no |
By anchoring rule
| Rule | Subjects |
|---|---|
| Root/cwd anchored | jj, Mercurial glob:, git pathspec, Bazel glob(), Ant, Gradle |
| Explicit per term | watchman (basename/wholename), Sapling (glob:/relglob:) |
| Float unless slashed | .gitignore, .hgignore, .npmignore, ripgrep, the ignore crate, sparse-checkout non-cone |
| Suffix anchored | rsync |
| Base name by default | fd, watchman match |
| No patterns at all | nixpkgs |
By kind-namespace shape
| Shape | Subjects | Modifier story |
|---|---|---|
word + : | Mercurial (16 kinds) | a new kind per combination |
hyphenated word + : | jj | -i suffix, still multiplying |
| symbol signature / word set | git pathspec | additive word set |
| closed enum | Sapling | exhaustive; a source change |
| term name + flag dictionary | watchman | additive flags |
| one element per primary | Ant | XML gives each its own |
| none — functions instead | Bazel, Mercurial predicates | n/a |
short aliases (g:, st:) | fff, hue today | ambiguous with paths |
Milestones
NOTE
Dates are the year the capability appeared in a release, taken from source copyright headers where one exists and from release history otherwise. Rows marked approx. could not be pinned to a primary source and should be treated as ordering, not as dates.
| Year | What landed |
|---|---|
| 1990 approx. | POSIX find standardises ! -a -o with -a binding above -o — shell |
| 1996 approx. | rsync ships --include/--exclude; the unified --filter rule language follows later — ordered |
| 2005 | .gitignore and git pathspec ship with git — ordered, pathspec |
| 2010 | Mercurial introduces filesets; filesetlang.py carries a 2010 copyright — hg |
| 2014 | bazel query's QueryParser.java — copyright 2014, open-sourced with Bazel — bazel |
| 2015 approx. | fzf's extended-search mode: implicit conjunction plus a tighter-binding | — implicit |
| 2016 | ripgrep adopts .gitignore glob rules for -g, plus the {a,b} extension — shell |
| 2000 approx. | Ant's <fileset> introduces ** ("matches zero or more directories") and the trailing-/ closure shorthand — ant |
| 2009 | Gradle's PatternFilterable inherits Ant's pattern language and adds intersect() — ant |
| 2013 approx. | watchman's generator/expression split — watchman |
| 2016 | ripgrep's ignore crate: a three-valued Match and a seven-step precedence fold — ignore |
| 2019 approx. | git sparse-checkout ships; cone mode follows, and non-cone is later deprecated — sparse |
| 2021 approx. | Sapling's pathmatcher: DirectoryMatch as a typed prune verdict — sapling |
| 2023 | nixpkgs lib.fileset — set algebra with its design decisions written down — nix |
| 2024 | jj filesets — Mercurial's idea with symbolic-only operators (fileset.pest, 2021–2024) — jj |
| 2025 approx. | fff's per-token constraint classification for a file picker — implicit |
Quick navigation
| If you want… | Read |
|---|---|
| The vocabulary these pages share | concepts |
| The closest prior art to what we are building | jj, then Mercurial |
| Why ordered rules cannot be the model | ordered, then the comparison's composition section |
| How a prefix namespace should be shaped | pathspec § kind namespace |
| Why complement and pruning are incompatible | concepts § lattice, ordered § pruning |
| What a generator is, and why it licenses complement | concepts § generators, then watchman |
| What an unprunable language costs later | sparse § evaluation |
| The typed shape of a prune verdict | sapling § evaluation, ignore § composition |
| Where the word "fileset" comes from | ant |
| The cost of classifying tokens without a delimiter | implicit |
| I am designing the sparkles fileset DSL | recommendations → contradictions → comparison |
Suggested reading paths
- Designing the language — concepts → jj → hg → comparison → recommendations.
- Implementing the parser — recommendations § grammar, diagnostics and worked examples, then jj § lexis and hg § lexis for the two positive charsets it is derived from.
- Implementing the planner — concepts § lattice, sapling (the typed verdict and its joins), sparse (what happens without one), pathspec § evaluation, ordered § pruning, ignore (the walker), hg § evaluation (the cost model).
- Migrating hue's picker — implicit, then recommendations O3 and O5.
Sources
Per-subject primary sources are listed in each deep-dive's Sources section. The surveyed revisions are: jj 88c3cd05, Bazel 7c3d22ad, git f78ce2f7, nixpkgs 82339b1b, ripgrep 3fce3b5b, fd 9e8927e8, fzf b1be3a8b, fff 3a0ce85c, rsync 8b8de523, watchman d99639db, Sapling 87b7db94, Ant a3a5ba18, Gradle cb109726, Mercurial tag 7.1, and GNU findutils 4.10.0.