`deep` option behaves differently from fast-glob
tinyglobby produces different results than fast-glob for some patterns when deep is set. Reproducible on win10 and archlinux (via WSL).
- My branch - https://github.com/outslept/tinyglobby/tree/fix/deep-option
- Test file - https://github.com/outslept/tinyglobby/blob/fix/deep-option/test/deep.test.mjs
Globstar with deep = 1 returns extra files compared to fast-glob. Parent-directory patterns (../**/*.txt) miss matches compared to fast-glob.
Thoughts:
normalizePatternmoves the root up, but the processed match stays relative to the original cwd (i.e., it still contains the../prefix). The walker crawls fromroot=parent(producing paths likechild/deep.txt), while the matcher expects paths relative to cwd (../child/deep.txt). That mismatch (root vs cwd) leads to no matches.- The
calculatedMaxDepthlooks correct in logs, so it shouldn't be an arithmetic issue I guess
does this only happen in main? does it also happen in 0.2.14?
UPD: This might be a smaller repro (test from branch fails as well.. sadly)
import fs from 'node:fs';
import path from 'node:path';
import { tmpdir } from 'node:os';
import fg from 'fast-glob';
import { glob } from 'tinyglobby';
const testDir = path.join(tmpdir(), 'glob-test-' + Date.now());
fs.mkdirSync(testDir, { recursive: true });
fs.mkdirSync(path.join(testDir, 'dir1'), { recursive: true });
fs.mkdirSync(path.join(testDir, 'dir1', 'dir2'), { recursive: true });
fs.writeFileSync(path.join(testDir, 'root.txt'), 'root');
fs.writeFileSync(path.join(testDir, 'dir1', 'file1.txt'), 'file1');
fs.writeFileSync(path.join(testDir, 'dir1', 'dir2', 'file2.txt'), 'file2');
fs.writeFileSync(path.join(testDir, '.hidden.txt'), 'hidden');
console.log('**/*.txt deep: 0');
console.log('tiny:', await glob('**/*.txt', { deep: 0, cwd: testDir }));
console.log('fast:', await fg('**/*.txt', { deep: 0, cwd: testDir }));
console.log('**/*.txt deep: 1');
console.log('tiny:', await glob('**/*.txt', { deep: 1, cwd: testDir }));
console.log('fast:', await fg('**/*.txt', { deep: 1, cwd: testDir }));
console.log('**/*.txt deep: 2');
console.log('tiny:', await glob('**/*.txt', { deep: 2, cwd: testDir }));
console.log('fast:', await fg('**/*.txt', { deep: 2, cwd: testDir }));
console.log('*.txt deep: 0');
console.log('tiny:', await glob('*.txt', { deep: 0, cwd: testDir }));
console.log('fast:', await fg('*.txt', { deep: 0, cwd: testDir }));
console.log('**/.*.txt deep: 1');
console.log('tiny:', await glob('**/.*.txt', { deep: 1, cwd: testDir }));
console.log('fast:', await fg('**/.*.txt', { deep: 1, cwd: testDir }));
fs.rmSync(testDir, { recursive: true });
hmm, so according to the results, fast-glob treats deep: 0 the same as deep: 1?
fast-glob
fast‑glob’s rule is straightforward and, once you get it, comforting in its consistency:
- At each directory, compute the directory’s level (number of path segments relative to the task base).
- If level >= deep, stop descending into that directory.
Short and mathematical. But let me put it another way, because metaphors help.
Imagine you’re on floor N of a building. The deep option is the ceiling level. If the ceiling is floor 2, you are not allowed to go to floor 2 or above. You can be at floor 1 and go no further down. That’s the rule.
Consequence (the thing people may trip on)
- deep = 0 → don’t enter any subdirectory at all.
- deep = 1 → still don’t enter first-level subdirectories (because their level is 1, and 1 >= 1).
- deep = 2 → you may enter first-level directories, but not second-level ones.
So deep = 0 and deep = 1 may look the same. That’s by design.
tinyglobby
tinyglobby’s implementation tries two things:
- It tries to find a crawl root by analyzing patterns (
normalizePattern). If you have patterns like../foo/**, tinyglobby can move the crawl root up to the parent directory and adjust an internaldepthOffsetto reflect how many parent steps were consumed. - It maps the user’s deep option to fdir’s
maxDepthby computing:fdirOptions.maxDepth = Math.round(options.deep - props.depthOffset);
tinyglobby’s mapping makes deep act like a “budget of steps you can take from the walker’s root.” That means deep = 1 lets you take one step down — first-level directories are crawled. That’s one floor deeper than fast‑glob’s ceiling semantics.
Analogy: tinyglobby had a handful of stair steps in your pocket. You were told “take up to X steps” and you do. fast‑glob instead says “hold up! stop when you reach floor X.” Subtle difference. Real consequences.
Soo.. Be aware of a possible migration gotcha!
1 base / task.base — the directory that a single task walks from. Depth is measured relative to this base. In fast-glob, patterns are grouped by their static base directory for efficiency.