MCPcopy
hub / github.com/codeaashu/claude-code / fileHistoryMakeSnapshot

Function fileHistoryMakeSnapshot

src/utils/fileHistory.ts:198–342  ·  view source on GitHub ↗
(
  updateFileHistoryState: (
    updater: (prev: FileHistoryState) => FileHistoryState,
  ) => void,
  messageId: UUID,
)

Source from the content-addressed store, hash-verified

196 * Adds a snapshot in the file history and backs up any modified tracked files.
197 */
198export async function fileHistoryMakeSnapshot(
199 updateFileHistoryState: (
200 updater: (prev: FileHistoryState) => FileHistoryState,
201 ) => void,
202 messageId: UUID,
203): Promise<void> {
204 if (!fileHistoryEnabled()) {
205 return undefined
206 }
207
208 // Phase 1: capture current state with a no-op updater so we know which
209 // files to back up. Returning the same reference keeps this a true no-op
210 // for any wrapper that honors same-ref returns (src/CLAUDE.md wrapper
211 // rule). Wrappers that unconditionally spread will trigger one extra
212 // re-render; acceptable for a once-per-turn call.
213 let captured: FileHistoryState | undefined
214 updateFileHistoryState(state => {
215 captured = state
216 return state
217 })
218 if (!captured) return // updateFileHistoryState was a no-op stub (e.g. mcp.ts)
219
220 // Phase 2: do all IO async, outside the updater.
221 const trackedFileBackups: Record<string, FileHistoryBackup> = {}
222 const mostRecentSnapshot = captured.snapshots.at(-1)
223 if (mostRecentSnapshot) {
224 logForDebugging(`FileHistory: Making snapshot for message ${messageId}`)
225 await Promise.all(
226 Array.from(captured.trackedFiles, async trackingPath => {
227 try {
228 const filePath = maybeExpandFilePath(trackingPath)
229 const latestBackup =
230 mostRecentSnapshot.trackedFileBackups[trackingPath]
231 const nextVersion = latestBackup ? latestBackup.version + 1 : 1
232
233 // Stat the file once; ENOENT means the tracked file was deleted.
234 let fileStats: Stats | undefined
235 try {
236 fileStats = await stat(filePath)
237 } catch (e: unknown) {
238 if (!isENOENT(e)) throw e
239 }
240
241 if (!fileStats) {
242 trackedFileBackups[trackingPath] = {
243 backupFileName: null, // Use null to denote missing tracked file
244 version: nextVersion,
245 backupTime: new Date(),
246 }
247 logEvent('tengu_file_history_backup_deleted_file', {
248 version: nextVersion,
249 })
250 logForDebugging(
251 `FileHistory: Missing tracked file: ${trackingPath}`,
252 )
253 return
254 }
255

Callers 3

submitMessageMethod · 0.85
executeUserInputFunction · 0.85
processInitialMessageFunction · 0.85

Calls 13

fileHistoryEnabledFunction · 0.85
updateFileHistoryStateFunction · 0.85
logForDebuggingFunction · 0.85
maybeExpandFilePathFunction · 0.85
statFunction · 0.85
isENOENTFunction · 0.85
logEventFunction · 0.85
checkOriginFileChangedFunction · 0.85
createBackupFunction · 0.85
maybeDumpStateForDebugFunction · 0.85

Tested by

no test coverage detected