Skip to content

Commit

Permalink
Correct the behavior for ignore files in sub-directories
Browse files Browse the repository at this point in the history
The ignore library expects for a subdirectory that the caller pass in
the relative path of files, i.e. remove the sub-directory prefix from
the filepath.
  • Loading branch information
Rob Leidle committed Aug 15, 2024
1 parent 7fb59c7 commit 81487d2
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 22 deletions.
73 changes: 52 additions & 21 deletions core/indexing/walkDir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ type WalkableEntry = {
// helper struct used for the DFS walk
type WalkContext = {
walkableEntry: WalkableEntry;
ignoreContexts: IgnoreContext[];
};

type IgnoreContext = {
ignore: Ignore;
dirname: string;
};

class DFSWalker {
Expand All @@ -51,30 +56,22 @@ class DFSWalker {
this.options.returnRelativePaths ? "" : this.path,
this.ide,
);
const root: WalkContext = {
walkableEntry: {
relPath: "",
absPath: this.path,
type: 2 as FileType.Directory,
entry: ["", 2 as FileType.Directory],
},
ignore: ignore().add(defaultIgnoreDir).add(defaultIgnoreFile),
};
const root = this.newRootWalkContext();
const stack = [root];
for (let cur = stack.pop(); cur; cur = stack.pop()) {
const walkableEntries = await this.listDirForWalking(cur.walkableEntry);
const ignore = await this.getIgnoreToApplyInDir(
cur.ignore,
const ignoreContexts = await this.getIgnoreToApplyInDir(
cur,
walkableEntries,
);
for (const w of walkableEntries) {
if (!this.shouldInclude(w, ignore)) {
if (!this.shouldInclude(w, ignoreContexts)) {
continue;
}
if (this.entryIsDirectory(w.entry)) {
stack.push({
walkableEntry: w,
ignore: ignore,
ignoreContexts: ignoreContexts,
});
if (this.options.onlyDirs) {
// when onlyDirs is enabled the walker will only return directory names
Expand All @@ -87,6 +84,23 @@ class DFSWalker {
}
}

private newRootWalkContext(): WalkContext {
return {
walkableEntry: {
relPath: "",
absPath: this.path,
type: 2 as FileType.Directory,
entry: ["", 2 as FileType.Directory],
},
ignoreContexts: [
{
ignore: ignore().add(defaultIgnoreDir).add(defaultIgnoreFile),
dirname: "",
},
],
};
}

private async listDirForWalking(
walkableEntry: WalkableEntry,
): Promise<WalkableEntry[]> {
Expand All @@ -102,15 +116,22 @@ class DFSWalker {
}

private async getIgnoreToApplyInDir(
parentIgnore: Ignore,
walkableEntries: WalkableEntry[],
): Promise<Ignore> {
const ignoreFilesInDir = await this.loadIgnoreFiles(walkableEntries);
curDir: WalkContext,
entriesInDir: WalkableEntry[],
): Promise<IgnoreContext[]> {
const ignoreFilesInDir = await this.loadIgnoreFiles(entriesInDir);
if (ignoreFilesInDir.length === 0) {
return parentIgnore;
return curDir.ignoreContexts;
}
const patterns = ignoreFilesInDir.map((c) => gitIgArrayFromFile(c)).flat();
return ignore().add(parentIgnore).add(patterns);
const newIgnoreContext = {
ignore: ignore().add(patterns),
dirname: curDir.walkableEntry.relPath,
};
return [
...curDir.ignoreContexts,
newIgnoreContext
];
}

private async loadIgnoreFiles(entries: WalkableEntry[]): Promise<string[]> {
Expand All @@ -126,7 +147,7 @@ class DFSWalker {
return this.ignoreFileNames.has(p);
}

private shouldInclude(walkableEntry: WalkableEntry, ignore: Ignore) {
private shouldInclude(walkableEntry: WalkableEntry, ignoreContexts: IgnoreContext[]) {
if (this.entryIsSymlink(walkableEntry.entry)) {
// If called from the root, a symlink either links to a real file in this repository,
// and therefore will be walked OR it linksto something outside of the repository and
Expand All @@ -141,7 +162,17 @@ class DFSWalker {
return false;
}
}
return !ignore.ignores(relPath);
for (const ig of ignoreContexts) {
// remove the directory name and path seperator from the match path, unless this an ignore file
// in the root directory
const prefixLength = ig.dirname.length === 0 ? 0 : ig.dirname.length + 1;
// The ignore library expects a path relative to the ignore file location
const matchPath = relPath.substring(prefixLength);
if (ig.ignore.ignores(matchPath)) {
return false;
}
}
return true;
}

private entryIsDirectory(entry: Entry) {
Expand Down
19 changes: 18 additions & 1 deletion core/test/walkDir.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import path from "path";
import { walkDir, WalkerOptions } from "../indexing/walkDir.js";
import ignore, { Ignore } from "ignore";
import { walkDir, walkDirAsync, WalkerOptions } from "../indexing/walkDir.js";
import FileSystemIde from "../util/filesystem.js";
import {
addToTestDir,
Expand Down Expand Up @@ -191,6 +192,22 @@ describe("walkDir", () => {
await expectPaths(["a.txt"], ["ignored_dir/b.txt", "ignored_dir/c/d.py"]);
});

test("gitignore in sub directory should only apply to subdirectory", async () => {
const files = [
[".gitignore", "abc"],
"a.txt",
"abc",
"xyz/",
["xyz/.gitignore", "xyz"],
"xyz/b.txt",
"xyz/c/",
"xyz/c/d.py",
"xyz/xyz",
];
addToTestDir(files);
await expectPaths(["a.txt", "xyz/b.txt", "xyz/c/d.py"], ["abc", "xyz/xyz"]);
});

test("should handle complex patterns in .gitignore", async () => {
const files = [
[".gitignore", "*.what\n!important.what\ntemp/\n/root_only.txt"],
Expand Down

0 comments on commit 81487d2

Please sign in to comment.