mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-07-13 08:13:11 +00:00
perf(cli): compile ignore patterns once
Long-running CLI processes rebuilt the same minimatch ASTs for every checked path. Compile patterns while loading ignore rules and reuse them until the rules are reloaded. Refs #1006
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { minimatch } from "minimatch";
|
||||
import { Minimatch } from "minimatch";
|
||||
import { fsPromises as fs, path } from "@/apps/cli/node-compat";
|
||||
|
||||
/**
|
||||
@@ -17,7 +17,7 @@ import { fsPromises as fs, path } from "@/apps/cli/node-compat";
|
||||
* Missing files (`.livesync/ignore` or `.gitignore`) are silently skipped.
|
||||
*/
|
||||
export class IgnoreRules {
|
||||
private patterns: string[] = [];
|
||||
private patterns: Minimatch[] = [];
|
||||
|
||||
constructor(private vaultPath: string) {}
|
||||
|
||||
@@ -108,7 +108,7 @@ export class IgnoreRules {
|
||||
`Remove it from .livesync/ignore or use a separate include/exclude file.`
|
||||
);
|
||||
}
|
||||
this.patterns.push(this._normalisePattern(raw));
|
||||
this.patterns.push(new Minimatch(this._normalisePattern(raw), { dot: true }));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,6 +124,6 @@ export class IgnoreRules {
|
||||
}
|
||||
// Normalise to forward slashes for minimatch.
|
||||
const normalised = relativePath.replace(/\\/g, "/");
|
||||
return this.patterns.some((p) => minimatch(normalised, p, { dot: true }));
|
||||
return this.patterns.some((pattern) => pattern.match(normalised));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const minimatchStats = vi.hoisted(() => ({ constructions: 0 }));
|
||||
|
||||
vi.mock("minimatch", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("minimatch")>();
|
||||
|
||||
class CountingMinimatch extends actual.Minimatch {
|
||||
constructor(pattern: string, options?: import("minimatch").MinimatchOptions) {
|
||||
super(pattern, options);
|
||||
minimatchStats.constructions++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...actual,
|
||||
Minimatch: CountingMinimatch,
|
||||
minimatch: (path: string, pattern: string, options?: import("minimatch").MinimatchOptions) =>
|
||||
new CountingMinimatch(pattern, options).match(path),
|
||||
};
|
||||
});
|
||||
|
||||
import { IgnoreRules } from "./IgnoreRules";
|
||||
|
||||
describe("IgnoreRules", () => {
|
||||
@@ -19,6 +40,10 @@ describe("IgnoreRules", () => {
|
||||
await fs.writeFile(path.join(ignoreDir, "ignore"), content, "utf-8");
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
minimatchStats.constructions = 0;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
|
||||
});
|
||||
@@ -55,6 +80,20 @@ describe("IgnoreRules", () => {
|
||||
});
|
||||
|
||||
describe("shouldIgnore", () => {
|
||||
it("compiles loaded patterns once", async () => {
|
||||
const vaultPath = await createVault();
|
||||
await writeIgnoreFile(vaultPath, "*.tmp\nbuild/\n");
|
||||
const rules = new IgnoreRules(vaultPath);
|
||||
await rules.load();
|
||||
|
||||
expect(rules.shouldIgnore("notes/readme.md")).toBe(false);
|
||||
expect(rules.shouldIgnore("notes/scratch.tmp")).toBe(true);
|
||||
expect(rules.shouldIgnore("build/output.js")).toBe(true);
|
||||
expect(rules.shouldIgnore("other.md")).toBe(false);
|
||||
|
||||
expect(minimatchStats.constructions).toBe(2);
|
||||
});
|
||||
|
||||
it("matches **/*.tmp against notes/scratch.tmp", async () => {
|
||||
const vaultPath = await createVault();
|
||||
await writeIgnoreFile(vaultPath, "*.tmp\n");
|
||||
|
||||
Reference in New Issue
Block a user