Skip to content

Commit 7d84b36

Browse files
committed
Implement a per-file caching mechanism for git diff generation to optimize performance using file modification timestamps
1 parent 66488c6 commit 7d84b36

1 file changed

Lines changed: 86 additions & 39 deletions

File tree

  • packages/vscode/src/commands/checkpoints-command/utils

‎packages/vscode/src/commands/checkpoints-command/utils/git-utils.ts‎

Lines changed: 86 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import * as vscode from 'vscode'
22
import { exec } from 'child_process'
33
import { Logger } from '@shared/utils/logger'
4+
import * as crypto from 'crypto'
5+
import * as fs from 'fs/promises'
6+
import * as os from 'os'
7+
import * as path from 'path'
48

59
const execAsync = (command: string, options: any): Promise<string> => {
610
return new Promise((resolve, reject) => {
@@ -55,64 +59,107 @@ export const get_git_diff = async (
5559
workspace_folder: vscode.WorkspaceFolder
5660
): Promise<string | null> => {
5761
try {
58-
let diff = ''
59-
try {
60-
// Add --binary to handle binary files correctly.
61-
// execAsync will reject if there are differences (exit code 1), so we catch it.
62-
diff = await execAsync('git diff --binary HEAD', {
63-
cwd: workspace_folder.uri.fsPath,
62+
const cwd = workspace_folder.uri.fsPath
63+
const tmp_dir = os.tmpdir()
64+
65+
// Get list of changed tracked files
66+
const tracked_files_output = await execAsync(
67+
'git diff -z --name-only HEAD',
68+
{
69+
cwd,
6470
encoding: 'utf8',
6571
maxBuffer: 50 * 1024 * 1024 // 50MB max
66-
})
67-
} catch (e: any) {
68-
if (e.status == 1 && e.stdout) {
69-
// Exit code 1 means there are differences, which is not an error for us.
70-
diff = e.stdout.toString()
71-
} else {
72-
// Other exit codes indicate a real error.
73-
throw e
7472
}
75-
}
73+
)
74+
const tracked_files = tracked_files_output
75+
.split('\0')
76+
.filter((f) => f.length > 0)
7677

77-
const untracked_files = (
78-
await execAsync('git ls-files --others --exclude-standard', {
79-
cwd: workspace_folder.uri.fsPath,
78+
// Get list of untracked files
79+
const untracked_files_output = await execAsync(
80+
'git ls-files -z --others --exclude-standard',
81+
{
82+
cwd,
8083
encoding: 'utf8'
81-
})
84+
}
8285
)
83-
.trim()
84-
.split('\n')
86+
const untracked_files = untracked_files_output
87+
.split('\0')
8588
.filter((f) => f.length > 0)
8689

87-
let untracked_diff = ''
88-
for (const file of untracked_files) {
90+
const all_files = [
91+
...tracked_files.map((f) => ({ file: f, is_untracked: false })),
92+
...untracked_files.map((f) => ({ file: f, is_untracked: true }))
93+
]
94+
95+
let total_diff = ''
96+
97+
for (const { file, is_untracked } of all_files) {
98+
const absolute_path = path.join(cwd, file)
99+
let mtime = 0
100+
89101
try {
90-
// Use git diff to create a patch for the new file, which handles binary files correctly.
91-
// execAsync will throw if there are differences, but the diff will be in stdout.
92-
const file_diff = await execAsync(
93-
`git diff --no-index --binary /dev/null "${file}"`,
94-
{
95-
cwd: workspace_folder.uri.fsPath,
96-
encoding: 'utf8',
97-
maxBuffer: 50 * 1024 * 1024
98-
}
99-
)
100-
untracked_diff += file_diff
102+
const stats = await fs.stat(absolute_path)
103+
mtime = Math.floor(stats.mtimeMs)
104+
} catch {
105+
// File might be deleted or inaccessible
106+
}
107+
108+
let diff_chunk = ''
109+
let cache_path = ''
110+
111+
if (mtime > 0) {
112+
const path_hash = crypto
113+
.createHash('md5')
114+
.update(absolute_path)
115+
.digest('hex')
116+
const cache_filename = `cwc-checkpoint-cache-${path_hash}-${mtime}.txt`
117+
cache_path = path.join(tmp_dir, cache_filename)
118+
119+
try {
120+
const cached_diff = await fs.readFile(cache_path, 'utf8')
121+
total_diff += cached_diff
122+
continue
123+
} catch {
124+
// Cache miss
125+
}
126+
}
127+
128+
try {
129+
const cmd = is_untracked
130+
? `git diff --no-index --binary /dev/null "${file}"`
131+
: `git diff --binary HEAD -- "${file}"`
132+
133+
diff_chunk = await execAsync(cmd, {
134+
cwd,
135+
encoding: 'utf8',
136+
maxBuffer: 50 * 1024 * 1024
137+
})
101138
} catch (err: any) {
102-
if (err.status == 1 && err.stdout) {
103-
// The diff output is in stdout of the error object
104-
untracked_diff += err.stdout.toString()
139+
if (err.status === 1 && err.stdout) {
140+
diff_chunk = err.stdout.toString()
105141
} else {
106142
Logger.warn({
107143
function_name: 'get_git_diff',
108-
message: `Could not create diff for untracked file: ${file}`,
144+
message: `Could not create diff for file: ${file}`,
109145
data: err
110146
})
111147
}
112148
}
149+
150+
if (diff_chunk) {
151+
total_diff += diff_chunk
152+
if (mtime > 0 && cache_path) {
153+
try {
154+
await fs.writeFile(cache_path, diff_chunk, 'utf8')
155+
} catch (e) {
156+
// Ignore cache write errors
157+
}
158+
}
159+
}
113160
}
114161

115-
return diff + untracked_diff
162+
return total_diff
116163
} catch (error) {
117164
Logger.error({
118165
function_name: 'get_git_diff',

0 commit comments

Comments
 (0)