Skip to content

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:

  1. 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.
  2. What precedence? & and difference above |, left-associative — the independent conclusion of jj and Mercurial, with bazel query's flat table as the counter-example and fzf's inverted one as the consequence of implicit conjunction.
  3. 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.
  4. 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.
  5. 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.
  6. What does it cost to skip the language and classify tokens by heuristic?fff's parser, which is the most honest available answer.
  7. 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 ​

SubjectEcosystemCategoryCompositionOperatorsComplementLink
jj filesetsRustVCS query languageset algebra| & ~ + prefix ~yesjj
jj revsetsRustVCS query languageset algebra + graph| & ~ + 4 graph levelsyesrevsets
Mercurial filesetsPythonVCS query languageset algebra| & - + and or and not !yeshg
Sapling pathmatcherRustVCS query languageset algebraAPI combinatorsnosapling
watchmanC++ / JSONFile-watching querygenerate then filterallof/anyof/notyeswatchman
bazel queryJavaBuild-graph queryset algebra^ + - / intersect union exceptnobazel
Bazel glob()StarlarkBuild-file file selectionordered rulesnonenobazel
git pathspecCCLI path limiterordered rules:! / :^nopathspec
git sparse-checkoutCWorking-tree filterordered rules → directory set! (non-cone only)per-rulesparse
.gitignoreCOrdered rule fileordered rules!per-ruleordered
rsync filter rulesCOrdered rule fileordered rules+ / - per lineper-ruleordered
.npmignore / filesJSOrdered rule fileordered rules!, polarity invertedper-ruleordered
CODEOWNERS—Ordered rule fileordered rulesnone (! removed)noordered
the ignore crateRustWalker + matcher libraryordered rules! in globsper-ruleignore
Ant <fileset>JavaBuild-file selectiontwo-layer hybrid<and>/<or>/<not>/<none>yesant
Gradle PatternSetJavaBuild-file selectiontwo-layer hybridintersect/union/negateyesant
nixpkgs lib.filesetNixBuild-input selectionset algebrafunctionsnonix
fzfGoInteractive finderimplicit conjunctionsigils + |per-termimplicit
fffRustInteractive finderimplicit conjunction! prefixper-termimplicit
GNU findCTree traversalexplicit + implicit AND! -a -o , + ( )yesshell
fdRustTree traversalordered rules! in globs, --andper-globshell
ripgrepRustContent search filterordered rules! in globsper-globshell

Taxonomy ​

By composition model ​

ModelSubjectsCan intersect?
Set algebrajj, jj revsets, Mercurial, Bazel query, nixpkgs, Saplingyes
Ordered rules.gitignore, rsync, npm, CODEOWNERS, Bazel glob(), git pathspec, sparse-checkout, the ignore crate, fd, ripgrepno
Generate then filterwatchmanwithin a generator
Two-layer hybridAnt, Gradlein the upper layer
Implicit conjunctionfzf, fff, findone axis only

By evaluation universe ​

UniverseSubjectsComplement affordable?
Materialised manifest or indexjj, revsets, Mercurial, Bazel query, fffyes
A self-maintained indexwatchmanwithin a generator's bound
A tree that must be walked.gitignore, pathspec, sparse-checkout, nixpkgs, fd/rg, the ignore crate, glob(), Ant/Gradle, Saplingno

By anchoring rule ​

RuleSubjects
Root/cwd anchoredjj, Mercurial glob:, git pathspec, Bazel glob(), Ant, Gradle
Explicit per termwatchman (basename/wholename), Sapling (glob:/relglob:)
Float unless slashed.gitignore, .hgignore, .npmignore, ripgrep, the ignore crate, sparse-checkout non-cone
Suffix anchoredrsync
Base name by defaultfd, watchman match
No patterns at allnixpkgs

By kind-namespace shape ​

ShapeSubjectsModifier story
word + :Mercurial (16 kinds)a new kind per combination
hyphenated word + :jj-i suffix, still multiplying
symbol signature / word setgit pathspecadditive word set
closed enumSaplingexhaustive; a source change
term name + flag dictionarywatchmanadditive flags
one element per primaryAntXML gives each its own
none — functions insteadBazel, Mercurial predicatesn/a
short aliases (g:, st:)fff, hue todayambiguous 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.

YearWhat 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
2010Mercurial introduces filesets; filesetlang.py carries a 2010 copyright — hg
2014bazel 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
2016ripgrep 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
2009Gradle's PatternFilterable inherits Ant's pattern language and adds intersect() — ant
2013 approx.watchman's generator/expression split — watchman
2016ripgrep'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
2023nixpkgs lib.fileset — set algebra with its design decisions written down — nix
2024jj 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 shareconcepts
The closest prior art to what we are buildingjj, then Mercurial
Why ordered rules cannot be the modelordered, then the comparison's composition section
How a prefix namespace should be shapedpathspec § kind namespace
Why complement and pruning are incompatibleconcepts § lattice, ordered § pruning
What a generator is, and why it licenses complementconcepts § generators, then watchman
What an unprunable language costs latersparse § evaluation
The typed shape of a prune verdictsapling § evaluation, ignore § composition
Where the word "fileset" comes fromant
The cost of classifying tokens without a delimiterimplicit
I am designing the sparkles fileset DSLrecommendations → contradictions → comparison

Suggested reading paths ​


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.