Skip to content

Commit 19e1fce

Browse files
committed
Fix ignore option disabling the gitignore option
The `ignore` option was forwarded to the search that looks for the ignore files, so a pattern like `**/.gi*` hid `.gitignore` from it and nothing was ignored at all. It is still forwarded, since that is what keeps the search out of unreadable directories, but the patterns that could name one of the searched ignore files are now dropped. Fixes #281
1 parent 4f95a14 commit 19e1fce

8 files changed

Lines changed: 624 additions & 36 deletions

File tree

‎index.d.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ export type Options = {
4646
4747
Gitignore patterns take priority over user patterns, matching Git's behavior. To include gitignored files, set this to `false`.
4848
49-
Performance: Globby reads `.gitignore` files before globbing. When there are no negation patterns (like `!important.log`) and no parent `.gitignore` files are found, it passes ignore patterns to fast-glob to skip traversing ignored directories entirely, which significantly improves performance for large `node_modules` or build directories. When negation patterns or parent `.gitignore` files are present, all filtering is done after traversal to ensure correct Git-compatible behavior. For optimal performance, prefer specific `.gitignore` patterns without negations, or use `ignoreFiles: '.gitignore'` to target only the root ignore file.
49+
The `ignore` option applies to the results only. A pattern that could name an ignore file, such as `'**\/.gitignore'`, is not used when searching for the ignore files, so it hides them from the results without disabling this option.
50+
51+
Performance: Globby reads `.gitignore` files before globbing and hands fast-glob patterns for the directories it can prove are ignored, so whole ignored directories (like large `node_modules` or build outputs) are skipped during traversal instead of being enumerated and filtered afterwards. This holds even when negation patterns (like `!important.log`) or parent `.gitignore` files are present: only the rules that provably cannot be re-included by a negation are used to skip directories, while the final filtering always matches Git's behavior. To read fewer ignore files, use `ignoreFiles: '.gitignore'` to target only the root ignore file.
5052
5153
@default false
5254
*/
@@ -57,6 +59,8 @@ export type Options = {
5759
5860
This is a more generic form of the `gitignore` option, allowing you to find ignore files with a [compatible syntax](http://git-scm.com/docs/gitignore). For instance, this works with Babel's `.babelignore`, Prettier's `.prettierignore`, or ESLint's `.eslintignore` files.
5961
62+
The `ignore` option applies to the results only, as with the `gitignore` option.
63+
6064
Performance tip: Using a specific path like `'.gitignore'` is much faster than recursive patterns.
6165
6266
@default undefined
@@ -137,6 +141,8 @@ export type GitignoreOptions = {
137141
/**
138142
Glob patterns to exclude from ignore file search.
139143
144+
Unlike the `ignore` option of `globby()`, which applies to the results only, this one excludes files from the search itself, so a pattern that names an ignore file stops that file from being read.
145+
140146
@default []
141147
*/
142148
readonly ignore?: string | readonly string[];

‎index.js‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
normalizeNegativePattern,
2222
adjustIgnorePatternsForParentDirectories,
2323
convertPatternsForFastGlob,
24+
convertIgnorePatternsForIgnoreFileSearch,
2425
findGitRoot,
2526
findGitRootSync,
2627
} from './utilities.js';
@@ -245,6 +246,12 @@ const buildIgnoreFilterResult = ({options, cwd, ignoreResult: {rules, matcher},
245246
};
246247
};
247248

249+
// The ignore files are searched for with the same options as the glob itself, except for `ignore`, which excludes paths from the results and must not decide which ignore files are read. The patterns that cannot hide one are still passed on, so the search keeps skipping the directories they name.
250+
const getIgnoreFileSearchOptions = (options, searchPatterns) => ({
251+
...options,
252+
ignore: convertIgnorePatternsForIgnoreFileSearch(options.ignore, searchPatterns),
253+
});
254+
248255
/**
249256
Apply ignore files to options and return the filter predicate.
250257
@@ -272,7 +279,7 @@ const applyIgnoreFilesAndGetFilter = async options => {
272279
// Enable parent .gitignore search when using gitignore option
273280
const includeParentIgnoreFiles = options.gitignore === true;
274281
const ignoreResult = ignoreFilesPatterns.length > 0
275-
? await getIgnorePatternsAndPredicate(ignoreFilesPatterns, options, includeParentIgnoreFiles)
282+
? await getIgnorePatternsAndPredicate(ignoreFilesPatterns, getIgnoreFileSearchOptions(options, ignoreFilesPatterns), includeParentIgnoreFiles)
276283
: {rules: [], matcher: false};
277284

278285
const globalGitRoot = globalIgnoreFile ? await findGitRoot(cwd, options.fs) : undefined;
@@ -309,7 +316,7 @@ const applyIgnoreFilesAndGetFilterSync = options => {
309316
// Enable parent .gitignore search when using gitignore option
310317
const includeParentIgnoreFiles = options.gitignore === true;
311318
const ignoreResult = ignoreFilesPatterns.length > 0
312-
? getIgnorePatternsAndPredicateSync(ignoreFilesPatterns, options, includeParentIgnoreFiles)
319+
? getIgnorePatternsAndPredicateSync(ignoreFilesPatterns, getIgnoreFileSearchOptions(options, ignoreFilesPatterns), includeParentIgnoreFiles)
313320
: {rules: [], matcher: false};
314321

315322
const globalGitRoot = globalIgnoreFile ? findGitRootSync(cwd, options.fs) : undefined;

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"fast-glob": "^3.3.3",
6868
"ignore": "^7.0.5",
6969
"is-path-inside": "^4.0.0",
70+
"micromatch": "^4.0.8",
7071
"slash": "^5.1.0",
7172
"unicorn-magic": "^0.4.0"
7273
},

‎readme.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ When enabled, globby searches for `.gitignore` files from the current working di
9393

9494
Gitignore patterns take priority over user patterns, matching Git's behavior. To include gitignored files, set this to `false`.
9595

96+
The `ignore` option applies to the results only. A pattern that could name an ignore file, such as `'**/.gitignore'`, is not used when searching for the ignore files, so it hides them from the results without disabling this option.
97+
9698
**Performance:** Globby reads `.gitignore` files before globbing and hands fast-glob patterns for the directories it can prove are ignored, so whole ignored directories (like large `node_modules` or build outputs) are skipped during traversal instead of being enumerated and filtered afterwards. This holds even when negation patterns (like `!important.log`) or parent `.gitignore` files are present: only the rules that provably cannot be re-included by a negation are used to skip directories, while the final filtering always matches Git's behavior. To read fewer ignore files, use `ignoreFiles: '.gitignore'` to target only the root ignore file.
9799

98100
##### globalGitignore
@@ -117,6 +119,8 @@ Glob patterns to look for ignore files, which are then used to ignore globbed fi
117119

118120
This is a more generic form of the `gitignore` option, allowing you to find ignore files with a [compatible syntax](http://git-scm.com/docs/gitignore). For instance, this works with Babel's `.babelignore`, Prettier's `.prettierignore`, or ESLint's `.eslintignore` files.
119121

122+
The `ignore` option applies to the results only, as with the [`gitignore`](#gitignore) option.
123+
120124
**Performance tip:** Using a specific path like `'.gitignore'` is much faster than recursive patterns.
121125

122126
##### expandNegationOnlyPatterns
@@ -248,6 +252,8 @@ Default: `[]`
248252

249253
Glob patterns to exclude from `.gitignore` file search.
250254

255+
Unlike the `ignore` option of [`globby()`](#options), which applies to the results only, this one excludes files from the search itself, so a pattern that names an ignore file stops that file from being read.
256+
251257
##### followSymbolicLinks
252258

253259
Type: `boolean`\

‎tests/globby.js‎

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
getPathValues,
2525
invalidPatterns,
2626
isUnique,
27+
runGlobby,
2728
} from './utilities.js';
2829

2930
const cwd = process.cwd();
@@ -39,39 +40,6 @@ const fixture = [
3940
'e.tmp',
4041
];
4142

42-
const stabilizeResult = result => result
43-
.map(fastGlobResult => {
44-
// In `objectMode`, `fastGlobResult.dirent` contains a function that makes `t.deepEqual` assertion fail.
45-
// `fastGlobResult.stats` contains different `atime`.
46-
if (typeof fastGlobResult === 'object') {
47-
const {dirent, stats, ...rest} = fastGlobResult;
48-
return rest;
49-
}
50-
51-
return fastGlobResult;
52-
})
53-
.sort((a, b) => (a.path ?? a).localeCompare(b.path ?? b));
54-
55-
const runGlobby = async (t, patterns, options) => {
56-
const syncResult = globbySync(patterns, options);
57-
const promiseResult = await globby(patterns, options);
58-
const streamResult = await globbyStream(patterns, options).toArray();
59-
60-
const result = stabilizeResult(promiseResult);
61-
t.deepEqual(
62-
stabilizeResult(syncResult),
63-
result,
64-
'globbySync() result is different than globby()',
65-
);
66-
t.deepEqual(
67-
stabilizeResult(streamResult),
68-
result,
69-
'globbyStream() result is different than globby()',
70-
);
71-
72-
return promiseResult;
73-
};
74-
7543
const blockNodeModulesTraversal = directory => {
7644
const normalizedDirectory = path.normalize(directory);
7745
const directoryPrefix = `${normalizedDirectory}${path.sep}`;

0 commit comments

Comments
 (0)