| 34 | export function reversePatch(structuredPatch: StructuredPatch[]): StructuredPatch[]; |
| 35 | export function reversePatch(structuredPatch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[]; |
| 36 | export function reversePatch(structuredPatch: StructuredPatch | StructuredPatch[]): StructuredPatch | StructuredPatch[] { |
| 37 | if (Array.isArray(structuredPatch)) { |
| 38 | // (See comment in unixToWin for why we need the pointless-looking anonymous function here) |
| 39 | return structuredPatch.map(patch => reversePatch(patch)).reverse(); |
| 40 | } |
| 41 | |
| 42 | const reversed: StructuredPatch = { |
| 43 | ...structuredPatch, |
| 44 | oldFileName: structuredPatch.isGit ? swapPrefix(structuredPatch.newFileName) : structuredPatch.newFileName, |
| 45 | oldHeader: structuredPatch.newHeader, |
| 46 | newFileName: structuredPatch.isGit ? swapPrefix(structuredPatch.oldFileName) : structuredPatch.oldFileName, |
| 47 | newHeader: structuredPatch.oldHeader, |
| 48 | oldMode: structuredPatch.newMode, |
| 49 | newMode: structuredPatch.oldMode, |
| 50 | isCreate: structuredPatch.isDelete, |
| 51 | isDelete: structuredPatch.isCreate, |
| 52 | hunks: structuredPatch.hunks.map(hunk => { |
| 53 | return { |
| 54 | oldLines: hunk.newLines, |
| 55 | oldStart: hunk.newStart, |
| 56 | newLines: hunk.oldLines, |
| 57 | newStart: hunk.oldStart, |
| 58 | lines: hunk.lines.map(l => { |
| 59 | if (l.startsWith('-')) { return `+${l.slice(1)}`; } |
| 60 | if (l.startsWith('+')) { return `-${l.slice(1)}`; } |
| 61 | return l; |
| 62 | }) |
| 63 | }; |
| 64 | }) |
| 65 | }; |
| 66 | |
| 67 | if (structuredPatch.isCopy) { |
| 68 | // Reversing a copy means deleting the file that was created by the copy. |
| 69 | // The "old" file in the reversed patch is the copy destination (which |
| 70 | // exists and should be removed), and the "new" file is /dev/null. |
| 71 | // |
| 72 | // Note: we clear the hunks because the original copy's hunks describe |
| 73 | // the diff between the source and destination, not the full content of |
| 74 | // the destination file, so they can't be meaningfully reversed into a |
| 75 | // deletion hunk. This means the resulting patch is not something |
| 76 | // `git apply` will accept (it requires deletion patches to include a |
| 77 | // hunk removing every line). Producing a correct deletion hunk would |
| 78 | // require knowing the full content of the copy destination, which we |
| 79 | // don't have. Consumers that need a `git apply`-compatible patch will |
| 80 | // need to resolve the full file content themselves. |
| 81 | reversed.newFileName = '/dev/null'; |
| 82 | reversed.newHeader = undefined; |
| 83 | reversed.isDelete = true; |
| 84 | delete reversed.isCreate; |
| 85 | delete reversed.isCopy; |
| 86 | delete reversed.isRename; |
| 87 | reversed.hunks = []; |
| 88 | } |
| 89 | // Reversing a rename is just a rename in the opposite direction; |
| 90 | // isRename stays set and the filenames are already swapped above. |
| 91 | |
| 92 | return reversed; |
| 93 | } |