spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / mods / diff / hooks / git / parse / parse-file-diffs / parse-file-diffs.ts
93 lines · 2.8 KBRaw
1import Limits from '../../../limits'
2import { EMPTY_FILE_HUNKS } from '../../empty-file-hunks'
3import type Types from '../../types'
4import { FILE_HEADER_START } from '../file-header-start'
5import { isWholeListing } from '../is-whole-listing'
6import { parseFileDiff } from '../parse-file-diff.js'
7import { QUOTED_HEADER_START } from '../quoted-header-start'
8import { UNMERGED_STATUS } from '../unmerged-status'
9
10/**
11 * The bodies one `git diff --raw -z -p` answer settles, by path: each file's
12 * patch parsed as one file's, under the name git listed for it.
13 *
14 * The n-th patch is the n-th NUL-ended name's, unmerged names having none and
15 * neighbours under one opening line being one file; an unquoted opening line
16 * must equal its name's. Where the host cut, a file waits unless large.
17 *
18 * @param stdout the child's standard output
19 * @param asked the paths the child was asked for
20 * @returns the settled bodies; none where names and patches disagree
21 */
22export function parseFileDiffs(
23 stdout: string,
24 asked: readonly string[],
25): ReadonlyMap<string, Types.FileHunks> {
26 const fields = stdout.split('\0')
27
28 const patchAt = fields.findIndex(
29 (field, at) => at % 2 === 0 && !field.startsWith(':'),
30 )
31
32 const listed = fields
33 .slice(0, patchAt)
34 .filter(
35 (_, at) => at % 2 === 1 && !fields[at - 1]?.endsWith(UNMERGED_STATUS),
36 )
37
38 const isParted = fields[patchAt] === ''
39
40 const rows = fields
41 .slice(patchAt + (isParted ? 1 : 0))
42 .join('\0')
43 .split('\n')
44
45 const opened = rows.flatMap((row, at) =>
46 row.startsWith(FILE_HEADER_START) ? [at] : [],
47 )
48
49 const starts = opened.filter(
50 (at, nth) => rows[at] !== rows[opened[nth - 1] ?? -1],
51 )
52
53 const isWhole = isWholeListing(stdout, '\n')
54
55 const arrived = starts
56 .map((start, nth) => {
57 const end = starts[nth + 1]
58 const path = listed[nth] ?? ''
59 const isLast = end === undefined
60
61 return {
62 path,
63 isNamed:
64 rows[start]?.startsWith(QUOTED_HEADER_START) === true ||
65 rows[start] === `${FILE_HEADER_START}a/${path} b/${path}`,
66 isCut: isLast && !isWhole,
67 text: rows.slice(start, end).join('\n') + (isLast ? '' : '\n'),
68 }
69 })
70 .filter(patch => !patch.isCut || patch.text.length > Limits.MAX_DIFF_BYTES)
71
72 const isPaired =
73 patchAt !== -1 &&
74 starts.length <= listed.length &&
75 (starts.length === listed.length || starts.length === 0 || !isWhole) &&
76 arrived.every(patch => patch.isNamed)
77
78 if (!isPaired) {
79 return new Map()
80 }
81
82 const read = new Map(
83 arrived.map(patch => [patch.path, parseFileDiff(patch.text)]),
84 )
85
86 const waiting = isWhole ? [] : listed.slice(arrived.length)
87
88 return new Map(
89 asked
90 .filter(path => !waiting.includes(path))
91 .map(path => [path, read.get(path) ?? EMPTY_FILE_HUNKS]),
92 )
93}