* Parses the old and new filenames from a `diff --git` header line. * * The format is: * diff --git a/ b/ * * When filenames contain special characters (including newlines, tabs, * backslashes, or double quotes), Git quotes them with C-style escaping: *
(line: string)
| 264 | * undefined in the output. |
| 265 | */ |
| 266 | function parseGitDiffHeader(line: string): { oldFileName: string, newFileName: string } | null { |
| 267 | // Strip the "diff --git " prefix |
| 268 | const rest = line.substring('diff --git '.length); |
| 269 | |
| 270 | // Handle quoted paths: "a/path" "b/path" |
| 271 | // Git quotes paths when they contain characters like newlines, tabs, |
| 272 | // backslashes, or double quotes (but notably not spaces). |
| 273 | if (rest.startsWith('"')) { |
| 274 | const oldPath = parseQuotedFileName(rest); |
| 275 | if (oldPath === null) { return null; } |
| 276 | const afterOld = rest.substring(oldPath.rawLength + 1); // +1 for space |
| 277 | let newFileName: string; |
| 278 | if (afterOld.startsWith('"')) { |
| 279 | const newPath = parseQuotedFileName(afterOld); |
| 280 | if (newPath === null) { return null; } |
| 281 | newFileName = newPath.fileName; |
| 282 | } else { |
| 283 | newFileName = afterOld; |
| 284 | } |
| 285 | return { |
| 286 | oldFileName: oldPath.fileName, |
| 287 | newFileName |
| 288 | }; |
| 289 | } |
| 290 | |
| 291 | // Check if the second path is quoted |
| 292 | // e.g. diff --git a/simple "b/renamed\nnewline.txt" |
| 293 | const quoteIdx = rest.indexOf('"'); |
| 294 | if (quoteIdx > 0) { |
| 295 | const oldFileName = rest.substring(0, quoteIdx - 1); |
| 296 | const newPath = parseQuotedFileName(rest.substring(quoteIdx)); |
| 297 | if (newPath === null) { return null; } |
| 298 | return { |
| 299 | oldFileName, |
| 300 | newFileName: newPath.fileName |
| 301 | }; |
| 302 | } |
| 303 | |
| 304 | // Unquoted paths. Try to find the split point. |
| 305 | // The format is: a/<old-path> b/<new-path> |
| 306 | // |
| 307 | // Note the potential ambiguity caused by the possibility of the file paths |
| 308 | // themselves containing the substring ` b/`, plus the pathological case |
| 309 | // described in the comment above. |
| 310 | // |
| 311 | // Strategy: find all occurrences of " b/" and split on the middle |
| 312 | // one. When old and new names are the same (which is the only case where |
| 313 | // we can't rely on extended headers later in the patch so HAVE to get |
| 314 | // this right), this will always be the correct split. |
| 315 | if (rest.startsWith('a/')) { |
| 316 | const splits = []; |
| 317 | let idx = 0; |
| 318 | while (true) { |
| 319 | idx = rest.indexOf(' b/', idx + 1); |
| 320 | if (idx === -1) { break; } |
| 321 | splits.push(idx); |
| 322 | } |
| 323 | if (splits.length > 0) { |
no test coverage detected