fix(core): accept padded patch markers (#38036)

This commit is contained in:
Aiden Cline 2026-07-20 23:44:17 -05:00 committed by GitHub
commit 96dc560833
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 47 additions and 13 deletions

View file

@ -54,19 +54,20 @@ export function parse(patchText: string): Result.Result<ReadonlyArray<Hunk>, Par
let index = begin + 1
while (index < end) {
const line = lines[index]!
if (line.startsWith("*** Add File:")) {
const path = line.slice("*** Add File:".length).trim()
const header = line.trim()
if (header.startsWith("*** Add File:")) {
const path = header.slice("*** Add File:".length).trim()
if (!path) {
index++
continue
}
const parsed = parseAdd(lines, index + 1)
const parsed = parseAdd(lines, index + 1, end)
hunks.push({ type: "add", path, contents: parsed.content })
index = parsed.next
continue
}
if (line.startsWith("*** Delete File:")) {
const path = line.slice("*** Delete File:".length).trim()
if (header.startsWith("*** Delete File:")) {
const path = header.slice("*** Delete File:".length).trim()
if (!path) {
index++
continue
@ -75,8 +76,8 @@ export function parse(patchText: string): Result.Result<ReadonlyArray<Hunk>, Par
index++
continue
}
if (line.startsWith("*** Update File:")) {
const path = line.slice("*** Update File:".length).trim()
if (header.startsWith("*** Update File:")) {
const path = header.slice("*** Update File:".length).trim()
if (!path) {
index++
continue
@ -87,7 +88,7 @@ export function parse(patchText: string): Result.Result<ReadonlyArray<Hunk>, Par
movePath = lines[next]!.slice("*** Move to:".length).trim()
next++
}
const parsed = parseUpdate(lines, next)
const parsed = parseUpdate(lines, next, end)
hunks.push({ type: "update", path, movePath, chunks: parsed.chunks })
index = parsed.next
continue
@ -122,20 +123,20 @@ export function joinBom(text: string, bom: boolean) {
return bom ? `\uFEFF${stripped}` : stripped
}
function parseAdd(lines: ReadonlyArray<string>, start: number) {
function parseAdd(lines: ReadonlyArray<string>, start: number, end: number) {
const content: string[] = []
let index = start
while (index < lines.length && !lines[index]!.startsWith("***")) {
while (index < end && !lines[index]!.startsWith("***")) {
if (lines[index]!.startsWith("+")) content.push(lines[index]!.slice(1))
index++
}
return { content: content.join("\n"), next: index }
}
function parseUpdate(lines: ReadonlyArray<string>, start: number) {
function parseUpdate(lines: ReadonlyArray<string>, start: number, end: number) {
const chunks: UpdateFileChunk[] = []
let index = start
while (index < lines.length && !lines[index]!.startsWith("***")) {
while (index < end && !lines[index]!.startsWith("***")) {
if (!lines[index]!.startsWith("@@")) {
index++
continue
@ -145,7 +146,7 @@ function parseUpdate(lines: ReadonlyArray<string>, start: number) {
const newLines: string[] = []
let endOfFile = false
index++
while (index < lines.length && !lines[index]!.startsWith("@@") && !lines[index]!.startsWith("***")) {
while (index < end && !lines[index]!.startsWith("@@") && !lines[index]!.startsWith("***")) {
const line = lines[index]!
if (line.startsWith(" ")) {
oldLines.push(line.slice(1))

View file

@ -58,6 +58,39 @@ describe("Patch", () => {
])
})
test("parses a whitespace-padded hunk header", () => {
expect(parse("*** Begin Patch\n *** Update File: foo.txt\n@@\n-old\n+new\n*** End Patch")).toEqual([
{
type: "update",
path: "foo.txt",
movePath: undefined,
chunks: [{ oldLines: ["old"], newLines: ["new"], changeContext: undefined, endOfFile: undefined }],
},
])
})
test("parses leading and trailing whitespace around patch markers", () => {
expect(parse(" *** Begin Patch\n*** Update File: file.txt\n@@\n-one\n+two\n*** End Patch ")).toEqual([
{
type: "update",
path: "file.txt",
movePath: undefined,
chunks: [{ oldLines: ["one"], newLines: ["two"], changeContext: undefined, endOfFile: undefined }],
},
])
})
test("parses whitespace on the inner sides of patch marker lines", () => {
expect(parse("*** Begin Patch \n*** Update File: file.txt\n@@\n-one\n+two\n *** End Patch")).toEqual([
{
type: "update",
path: "file.txt",
movePath: undefined,
chunks: [{ oldLines: ["one"], newLines: ["two"], changeContext: undefined, endOfFile: undefined }],
},
])
})
test("derives fuzzy line updates while preserving BOM", () => {
const update = Patch.derive("update.txt", [{ oldLines: [" old "], newLines: ["new"] }], "\uFEFFold\n")
expect(update).toEqual({ content: "new\n", bom: true })