123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.verifyHunkIntegrity = exports.parsePatchFile = exports.interpretParsedPatchFile = exports.EXECUTABLE_FILE_MODE = exports.NON_EXECUTABLE_FILE_MODE = exports.parseHunkHeaderLine = void 0;
- const assertNever_1 = require("../assertNever");
- const parseHunkHeaderLine = (headerLine) => {
- const match = headerLine
- .trim()
- .match(/^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@.*/);
- if (!match) {
- throw new Error(`Bad header line: '${headerLine}'`);
- }
- return {
- original: {
- start: Math.max(Number(match[1]), 1),
- length: Number(match[3] || 1),
- },
- patched: {
- start: Math.max(Number(match[4]), 1),
- length: Number(match[6] || 1),
- },
- };
- };
- exports.parseHunkHeaderLine = parseHunkHeaderLine;
- exports.NON_EXECUTABLE_FILE_MODE = 0o644;
- exports.EXECUTABLE_FILE_MODE = 0o755;
- const emptyFilePatch = () => ({
- diffLineFromPath: null,
- diffLineToPath: null,
- oldMode: null,
- newMode: null,
- deletedFileMode: null,
- newFileMode: null,
- renameFrom: null,
- renameTo: null,
- beforeHash: null,
- afterHash: null,
- fromPath: null,
- toPath: null,
- hunks: null,
- });
- const emptyHunk = (headerLine) => ({
- header: exports.parseHunkHeaderLine(headerLine),
- parts: [],
- source: "",
- });
- const hunkLinetypes = {
- "@": "header",
- "-": "deletion",
- "+": "insertion",
- " ": "context",
- "\\": "pragma",
-
- undefined: "context",
- "\r": "context",
- };
- function parsePatchLines(lines, { supportLegacyDiffs }) {
- const result = [];
- let currentFilePatch = emptyFilePatch();
- let state = "parsing header";
- let currentHunk = null;
- let currentHunkMutationPart = null;
- let hunkStartLineIndex = 0;
- function commitHunk(i) {
- if (currentHunk) {
- if (currentHunkMutationPart) {
- currentHunk.parts.push(currentHunkMutationPart);
- currentHunkMutationPart = null;
- }
- currentHunk.source = lines.slice(hunkStartLineIndex, i).join("\n");
- currentFilePatch.hunks.push(currentHunk);
- currentHunk = null;
- }
- }
- function commitFilePatch(i) {
- commitHunk(i);
- result.push(currentFilePatch);
- currentFilePatch = emptyFilePatch();
- }
- for (let i = 0; i < lines.length; i++) {
- const line = lines[i];
- if (state === "parsing header") {
- if (line.startsWith("@@")) {
- hunkStartLineIndex = i;
- state = "parsing hunks";
- currentFilePatch.hunks = [];
- i--;
- }
- else if (line.startsWith("diff --git ")) {
- if (currentFilePatch && currentFilePatch.diffLineFromPath) {
- commitFilePatch(i);
- }
- const match = line.match(/^diff --git a\/(.*?) b\/(.*?)\s*$/);
- if (!match) {
- throw new Error("Bad diff line: " + line);
- }
- currentFilePatch.diffLineFromPath = match[1];
- currentFilePatch.diffLineToPath = match[2];
- }
- else if (line.startsWith("old mode ")) {
- currentFilePatch.oldMode = line.slice("old mode ".length).trim();
- }
- else if (line.startsWith("new mode ")) {
- currentFilePatch.newMode = line.slice("new mode ".length).trim();
- }
- else if (line.startsWith("deleted file mode ")) {
- currentFilePatch.deletedFileMode = line
- .slice("deleted file mode ".length)
- .trim();
- }
- else if (line.startsWith("new file mode ")) {
- currentFilePatch.newFileMode = line
- .slice("new file mode ".length)
- .trim();
- }
- else if (line.startsWith("rename from ")) {
- currentFilePatch.renameFrom = line.slice("rename from ".length).trim();
- }
- else if (line.startsWith("rename to ")) {
- currentFilePatch.renameTo = line.slice("rename to ".length).trim();
- }
- else if (line.startsWith("index ")) {
- const match = line.match(/(\w+)\.\.(\w+)/);
- if (!match) {
- continue;
- }
- currentFilePatch.beforeHash = match[1];
- currentFilePatch.afterHash = match[2];
- }
- else if (line.startsWith("--- ")) {
- currentFilePatch.fromPath = line.slice("--- a/".length).trim();
- }
- else if (line.startsWith("+++ ")) {
- currentFilePatch.toPath = line.slice("+++ b/".length).trim();
- }
- }
- else {
- if (supportLegacyDiffs && line.startsWith("--- a/")) {
- state = "parsing header";
- commitFilePatch(i);
- i--;
- continue;
- }
-
- const lineType = hunkLinetypes[line[0]] || null;
- switch (lineType) {
- case "header":
- commitHunk(i);
- currentHunk = emptyHunk(line);
- break;
- case null:
-
- state = "parsing header";
- commitFilePatch(i);
- i--;
- break;
- case "pragma":
- if (!line.startsWith("\\ No newline at end of file")) {
- throw new Error("Unrecognized pragma in patch file: " + line);
- }
- if (!currentHunkMutationPart) {
- throw new Error("Bad parser state: No newline at EOF pragma encountered without context");
- }
- currentHunkMutationPart.noNewlineAtEndOfFile = true;
- break;
- case "insertion":
- case "deletion":
- case "context":
- if (!currentHunk) {
- throw new Error("Bad parser state: Hunk lines encountered before hunk header");
- }
- if (currentHunkMutationPart &&
- currentHunkMutationPart.type !== lineType) {
- currentHunk.parts.push(currentHunkMutationPart);
- currentHunkMutationPart = null;
- }
- if (!currentHunkMutationPart) {
- currentHunkMutationPart = {
- type: lineType,
- lines: [],
- noNewlineAtEndOfFile: false,
- };
- }
- currentHunkMutationPart.lines.push(line.slice(1));
- break;
- default:
-
- assertNever_1.assertNever(lineType);
- }
- }
- }
- commitFilePatch(lines.length);
- for (const { hunks } of result) {
- if (hunks) {
- for (const hunk of hunks) {
- verifyHunkIntegrity(hunk);
- }
- }
- }
- return result;
- }
- function interpretParsedPatchFile(files) {
- const result = [];
- for (const file of files) {
- const { diffLineFromPath, diffLineToPath, oldMode, newMode, deletedFileMode, newFileMode, renameFrom, renameTo, beforeHash, afterHash, fromPath, toPath, hunks, } = file;
- const type = renameFrom
- ? "rename"
- : deletedFileMode
- ? "file deletion"
- : newFileMode
- ? "file creation"
- : hunks && hunks.length > 0
- ? "patch"
- : "mode change";
- let destinationFilePath = null;
- switch (type) {
- case "rename":
- if (!renameFrom || !renameTo) {
- throw new Error("Bad parser state: rename from & to not given");
- }
- result.push({
- type: "rename",
- fromPath: renameFrom,
- toPath: renameTo,
- });
- destinationFilePath = renameTo;
- break;
- case "file deletion": {
- const path = diffLineFromPath || fromPath;
- if (!path) {
- throw new Error("Bad parse state: no path given for file deletion");
- }
- result.push({
- type: "file deletion",
- hunk: (hunks && hunks[0]) || null,
- path,
- mode: parseFileMode(deletedFileMode),
- hash: beforeHash,
- });
- break;
- }
- case "file creation": {
- const path = diffLineToPath || toPath;
- if (!path) {
- throw new Error("Bad parse state: no path given for file creation");
- }
- result.push({
- type: "file creation",
- hunk: (hunks && hunks[0]) || null,
- path,
- mode: parseFileMode(newFileMode),
- hash: afterHash,
- });
- break;
- }
- case "patch":
- case "mode change":
- destinationFilePath = toPath || diffLineToPath;
- break;
- default:
- assertNever_1.assertNever(type);
- }
- if (destinationFilePath && oldMode && newMode && oldMode !== newMode) {
- result.push({
- type: "mode change",
- path: destinationFilePath,
- oldMode: parseFileMode(oldMode),
- newMode: parseFileMode(newMode),
- });
- }
- if (destinationFilePath && hunks && hunks.length) {
- result.push({
- type: "patch",
- path: destinationFilePath,
- hunks,
- beforeHash,
- afterHash,
- });
- }
- }
- return result;
- }
- exports.interpretParsedPatchFile = interpretParsedPatchFile;
- function parseFileMode(mode) {
-
- const parsedMode = parseInt(mode, 8) & 0o777;
- if (parsedMode !== exports.NON_EXECUTABLE_FILE_MODE &&
- parsedMode !== exports.EXECUTABLE_FILE_MODE) {
- throw new Error("Unexpected file mode string: " + mode);
- }
- return parsedMode;
- }
- function parsePatchFile(file) {
- const lines = file.split(/\n/g);
- if (lines[lines.length - 1] === "") {
- lines.pop();
- }
- try {
- return interpretParsedPatchFile(parsePatchLines(lines, { supportLegacyDiffs: false }));
- }
- catch (e) {
- if (e instanceof Error &&
- e.message === "hunk header integrity check failed") {
- return interpretParsedPatchFile(parsePatchLines(lines, { supportLegacyDiffs: true }));
- }
- throw e;
- }
- }
- exports.parsePatchFile = parsePatchFile;
- function verifyHunkIntegrity(hunk) {
-
- let originalLength = 0;
- let patchedLength = 0;
- for (const { type, lines } of hunk.parts) {
- switch (type) {
- case "context":
- patchedLength += lines.length;
- originalLength += lines.length;
- break;
- case "deletion":
- originalLength += lines.length;
- break;
- case "insertion":
- patchedLength += lines.length;
- break;
- default:
- assertNever_1.assertNever(type);
- }
- }
- if (originalLength !== hunk.header.original.length ||
- patchedLength !== hunk.header.patched.length) {
- throw new Error("hunk header integrity check failed");
- }
- }
- exports.verifyHunkIntegrity = verifyHunkIntegrity;
|