(patch: StructuredPatch | StructuredPatch[], headerOptions?: HeaderOptions)
| 356 | * headers for patches with no hunks (e.g. renames without content changes). |
| 357 | */ |
| 358 | export function formatPatch(patch: StructuredPatch | StructuredPatch[], headerOptions?: HeaderOptions): string { |
| 359 | if (!headerOptions) { |
| 360 | headerOptions = INCLUDE_HEADERS; |
| 361 | } |
| 362 | if (Array.isArray(patch)) { |
| 363 | if (patch.length > 1 && !headerOptions.includeFileHeaders && !patch.every(p => p.isGit)) { |
| 364 | throw new Error( |
| 365 | 'Cannot omit file headers on a multi-file patch. ' |
| 366 | + '(The result would be unparseable; how would a tool trying to apply ' |
| 367 | + 'the patch know which changes are to which file?)' |
| 368 | ); |
| 369 | } |
| 370 | return patch.map(p => formatPatch(p, headerOptions)).join('\n'); |
| 371 | } |
| 372 | |
| 373 | const ret = []; |
| 374 | |
| 375 | // Git patches have a fixed header format (diff --git, extended headers, |
| 376 | // and ---/+++ when hunks are present), so headerOptions is ignored. |
| 377 | if (patch.isGit) { |
| 378 | headerOptions = INCLUDE_HEADERS; |
| 379 | // Emit Git-style diff --git header and extended headers. |
| 380 | // Git never puts /dev/null in the "diff --git" line; for file |
| 381 | // creations/deletions it uses the real filename on both sides. |
| 382 | if (!patch.oldFileName) { |
| 383 | throw new Error('oldFileName must be specified for Git patches'); |
| 384 | } |
| 385 | if (!patch.newFileName) { |
| 386 | throw new Error('newFileName must be specified for Git patches'); |
| 387 | } |
| 388 | let gitOldName = patch.oldFileName; |
| 389 | let gitNewName = patch.newFileName; |
| 390 | if (patch.isCreate && gitOldName === '/dev/null') { |
| 391 | gitOldName = gitNewName.replace(/^b\//, 'a/'); |
| 392 | } else if (patch.isDelete && gitNewName === '/dev/null') { |
| 393 | gitNewName = gitOldName.replace(/^a\//, 'b/'); |
| 394 | } |
| 395 | ret.push('diff --git ' + quoteFileNameIfNeeded(gitOldName) + ' ' + quoteFileNameIfNeeded(gitNewName)); |
| 396 | if (patch.isDelete) { |
| 397 | ret.push('deleted file mode ' + (patch.oldMode ?? '100644')); |
| 398 | } |
| 399 | if (patch.isCreate) { |
| 400 | ret.push('new file mode ' + (patch.newMode ?? '100644')); |
| 401 | } |
| 402 | if (patch.oldMode && patch.newMode && !patch.isDelete && !patch.isCreate) { |
| 403 | ret.push('old mode ' + patch.oldMode); |
| 404 | ret.push('new mode ' + patch.newMode); |
| 405 | } |
| 406 | if (patch.isRename) { |
| 407 | ret.push('rename from ' + quoteFileNameIfNeeded((patch.oldFileName ?? '').replace(/^a\//, ''))); |
| 408 | ret.push('rename to ' + quoteFileNameIfNeeded((patch.newFileName ?? '').replace(/^b\//, ''))); |
| 409 | } |
| 410 | if (patch.isCopy) { |
| 411 | ret.push('copy from ' + quoteFileNameIfNeeded((patch.oldFileName ?? '').replace(/^a\//, ''))); |
| 412 | ret.push('copy to ' + quoteFileNameIfNeeded((patch.newFileName ?? '').replace(/^b\//, ''))); |
| 413 | } |
| 414 | } else { |
| 415 | if (headerOptions.includeIndex && patch.oldFileName == patch.newFileName && patch.oldFileName !== undefined) { |
no test coverage detected