Skip to content

Commit 6e4656e

Browse files
committed
Performance: implement memoization for git diff
1 parent 2b66df4 commit 6e4656e

File tree

2 files changed

+37
-8
lines changed

2 files changed

+37
-8
lines changed

src/git.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,17 @@ describe("git", () => {
2525
expect(diffFromFile).toContain("diff --git");
2626
expect(diffFromFile).toContain("@@");
2727
});
28+
29+
it("should hit the cached diff of a file", () => {
30+
jest.mock("child_process").resetAllMocks();
31+
mockedChildProcess.execSync.mockReturnValue(Buffer.from(hunks));
32+
33+
const diffFromFileA = getDiffForFile("./mockfileCache.js");
34+
const diffFromFileB = getDiffForFile("./mockfileCache.js");
35+
expect(mockedChildProcess.execSync).toHaveBeenCalledTimes(1);
36+
expect(diffFromFileA).toEqual(diffFromFileB);
37+
38+
getDiffForFile("./mockfileMiss.js");
39+
expect(mockedChildProcess.execSync).toHaveBeenCalledTimes(2);
40+
});
2841
});

src/git.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,30 @@ import { Range } from "./Range";
55
const sanitizeFilePath = (filePath: string) =>
66
JSON.stringify(path.resolve(filePath));
77

8-
const getDiffForFile = (filePath: string, staged = false): string =>
9-
child_process
10-
.execSync(
11-
`git diff --diff-filter=ACM --unified=0 HEAD ${
12-
staged ? " --staged" : ""
13-
} -- ${sanitizeFilePath(filePath)}`
14-
)
15-
.toString();
8+
const diffCacheKey = (filePath: string, staged: boolean): string =>
9+
JSON.stringify([path.resolve(filePath), staged]);
10+
11+
const diffCache = new Map<string, string>();
12+
13+
const getDiffForFile = (filePath: string, staged = false): string => {
14+
// Get diff entry from cache
15+
const cachedDiff = diffCache.get(diffCacheKey(filePath, staged));
16+
if (cachedDiff) {
17+
// If entry is not falsy return it
18+
return cachedDiff;
19+
} else {
20+
// Otherwise spawn child_process set it in cache and return it
21+
const diff = child_process
22+
.execSync(
23+
`git diff --diff-filter=ACM --unified=0 HEAD ${
24+
staged ? " --staged" : ""
25+
} -- ${sanitizeFilePath(filePath)}`
26+
)
27+
.toString();
28+
diffCache.set(diffCacheKey(filePath, staged), diff);
29+
return diff;
30+
}
31+
};
1632

1733
const isHunkHeader = (input: string) => {
1834
const hunkHeaderRE = new RegExp(/^@@ .* @@/g);

0 commit comments

Comments
 (0)