fileset #
Filter files from a directory with a gitignore-like manifest. Given a fileset
manifest and a source directory, fileset copies the included files into an
output directory, preserving relative structure — a general-purpose pre-filter, so
excluded paths (e.g. secrets/) never reach whatever consumes the pruned tree.
Everything under the source is included by default; the manifest's rules subtract from
that set (and ! can add back). It is not a .gitignore: it never touches git
tracking, only what gets copied.
CLI #
fileset --fileset <manifest> <src> <out>
Copies every file under <src> that <manifest> includes into <out>. Symlinks are
skipped and .git/.jj are pruned before descent. Prints a copied-file count to
stderr.
As a standalone Nix flake (invoked by path):
nix run ./experimental/fileset -- --fileset ./owl.fileset.txt . /tmp/out
nix build ./experimental/fileset # -> result/bin/fileset
nix flake check ./experimental/fileset
Library #
The crate also exposes a small library (fileset):
use fileset::{Fileset, filter_tree};
let manifest = Fileset::load(Path::new("owl.fileset.txt"))?;
let included = manifest.includes(Path::new("src/main.rs")); // -> bool
let n = filter_tree(Path::new("."), Path::new("/tmp/out"), &manifest)?; // walk + copy
Fileset::load(&Path)— parse a manifest, inlining@imports.Fileset::includes(&Path)— whether a tree-root-relative path is included.filter_tree(src, out, &Fileset)— copy every included file undersrcintoout, returning the count.
Manifest format (gitignore-like) #
# Line comment. Blank lines are ignored.
<glob> Exclude paths matching this glob.
!<glob> Re-include paths matching this glob (negation).
@import <path> Inline another fileset / .gitignore-style file, with <path>
resolved relative to THIS file's directory.
Later rules win, and @import inlines its rules at that position — so ordering and
negation across an import boundary are well-defined. Imported .gitignore files are
read under these same rules (a best-effort superset of gitignore syntax).
Matching #
Each glob is compiled by the globset crate with
literal_separator(true), anchored gitignore-style:
*?— match within a single path segment (never across/)**— spans path segments{a,b}[0-9]— alternation / character classes
A pattern containing a / (e.g. vendor/lib/) is anchored to the tree root; a
pattern with no / (e.g. *.lock, node_modules/) matches at any depth. A
trailing / matches a directory and everything under it. Paths are matched relative to
the source root.
Ordering #
Since the last matching rule wins, list subtree exclusions last — after every !
negation — so a negation can never re-include a file that lives inside an excluded tree
(this is what keeps something like secrets/ absolute).