UNPKG

@dev-build-deploy/commit-it

Version:
221 lines (220 loc) 8.57 kB
"use strict"; /* * SPDX-FileCopyrightText: 2023 Kevin de Jong <monkaii@hotmail.com> * SPDX-License-Identifier: MIT */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getCommitFromHash = exports.gitObjectFolder = void 0; const assert_1 = __importDefault(require("assert")); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const zlib = __importStar(require("zlib")); const ccommit = __importStar(require("./commit")); /** @internal */ exports.gitObjectFolder = ".git/objects"; /** * Converts seconds since epoch and timezone information to a Date object * @param epoch Seconds since epoch * @param timezone Timezone offset in minutes * @returns Date object */ const dateToUTC = (epoch, timezone) => new Date(epoch * 1000 - timezone * 60000); /** * Splits the provided string into a name and date object; * Example: * "John Doe 1686937738 +0200" => { name: "John Doe", date: Date(2023-06-16T14:28:58.000Z) } * * @param value String to split * @returns Name and date object */ function extractNameAndDate(value) { if (value === undefined) return undefined; const valueSplit = value.split(" "); const [epoch, timezone] = valueSplit.splice(-2).map(v => Number.parseInt(v)); return { name: valueSplit.splice(0, value.length - 2).join(" "), date: dateToUTC(epoch, timezone), }; } /** * Extracts Key/Value pairs from the commit message * @param commit Commit message * @param key Key to extract * @returns Value of the key */ function getValueFromKey(commit, key) { const match = new RegExp("^" + key + " (.*)$", "m").exec(commit); return match ? match[1] : undefined; } /** * Parses a commit message into a Commit object * @param commit Commit message * @param hash Commit hash * @returns Commit object */ function parseCommitMessage(commit, hash) { const author = extractNameAndDate(getValueFromKey(commit, "author")); const committer = extractNameAndDate(getValueFromKey(commit, "committer")); const raw = commit .split(/^[\r\n]+/m) .splice(1) .join("\n") .trim(); return { raw, hash, author, committer, ...ccommit.parseCommitMessage(raw) }; } /** * Reads a (local) commit message from the .git/objects folder * @param hash Hash of the commit to read * @returns Commit object or undefined if the commit could not be found in the local repository */ function getCommitFromLocalObjects(hash, rootPath) { const objectPath = path.join(rootPath, exports.gitObjectFolder, hash.substring(0, 2), "/", hash.substring(2)); if (!fs.existsSync(objectPath)) return undefined; const commit = zlib.inflateSync(fs.readFileSync(objectPath)).toString(); return parseCommitMessage(commit, hash); } /** * Iterates through all pack files and returns the commit message for the provided hash * @param hash Hash of the commit to read * @param rootPath Path to the git repository * @returns Commit object or undefined if the commit could not be found in the pack files */ function getCommitFromPackFile(hash, rootPath) { for (const entry of fs.readdirSync(path.join(rootPath, exports.gitObjectFolder, "pack"))) { const filePath = path.join(rootPath, exports.gitObjectFolder, "pack", entry); if (path.extname(filePath) !== ".idx") continue; const hashes = readIdxFile(filePath); if (hashes[hash] === undefined) continue; const commit = readPackFile(filePath.replace(".idx", ".pack"), hashes[hash].readUint32BE()); return parseCommitMessage(commit, hash); } } /** * Returns a Commit object for the provided hash * @param hash Hash of the commit to read * @returns Commit object * @internal */ function getCommitFromHash(hash, rootPath) { if (!fs.existsSync(path.join(rootPath, exports.gitObjectFolder))) throw new Error(`Invalid git folder specified (${path.join(rootPath, exports.gitObjectFolder)})`); let message = getCommitFromLocalObjects(hash, rootPath); if (message !== undefined) return message; message = getCommitFromPackFile(hash, rootPath); if (message !== undefined) return message; throw new Error("Could not find commit message for hash " + hash); } exports.getCommitFromHash = getCommitFromHash; /** * Retrieves a single listing (block) from an git index file * @param idxBuffer Buffer containing the index file * @param index Starting index to read from * @param length Length (in bytes) of a single entry * @param size Number of entries to read * @returns Object containing the data and the index after reading */ function readIdxListing(idxBuffer, index, length, size) { const result = []; const end = index + size * length; while (index < end) { result.push(idxBuffer.subarray(index, index + length)); index += length; } return { data: result, index: index }; } /** * Reads a file from the file system from the specified index and length * @param file File to read * @param index Starting index to read from * @param length Length (in bytes) to read * @returns Buffer containing the data */ function readFile(file, index, length) { const buffer = Buffer.alloc(length); const fd = fs.openSync(file, "r"); fs.readSync(fd, buffer, 0, length, index); fs.closeSync(fd); return buffer; } /** * Parses a git Index file and returns a hashmap of all SHA's and their offsets in the pack file * @param path Path to the .idx file * @returns Hashmap of SHA's and their offsets in the pack file */ function readIdxFile(path) { const idxFile = fs.readFileSync(path); const version = idxFile.readUint32BE(4); (0, assert_1.default)(version === 2); const fanout = []; for (let index = 0; index < 256; index++) { const byte = idxFile.subarray(8 + index * 4, 8 + (index + 1) * 4); fanout.push(byte.toString("hex")); } const size = Number.parseInt(fanout[fanout.length - 1], 16); const { data: shaListing, index: endSha } = readIdxListing(idxFile, 1032, 20, size); const { index: endCrc } = readIdxListing(idxFile, endSha, 4, size); const { data: packFileOffsets } = readIdxListing(idxFile, endCrc, 4, size); const hashMap = {}; shaListing.forEach((sha, index) => { hashMap[sha.toString("hex")] = packFileOffsets[index]; }); return hashMap; } /** * Reads a git pack file and returns the contents of the specified index * @param path Path to the .pack file * @param index Index of the object to read * @returns Contents of the object */ function readPackFile(path, index) { const header = readFile(path, 0, 8); (0, assert_1.default)(header.subarray(0, 4).toString() === "PACK"); const version = header.readUint32BE(4); (0, assert_1.default)(version === 2); const entry = readFile(path, index, 20); let hunk = entry.readUint8(0); const type = (hunk & 0x70) >> 4; (0, assert_1.default)(type === 1); // TODO; Support other types let size = (hunk & 0x70) >> 4; let lastHunk = (hunk & 0x80) !== 0x80; let hunkId = 1; while (!lastHunk) { hunk = entry.readUint8(0 + hunkId); size |= (hunk & 0x7f) << (4 + 7 * (hunkId - 1)); lastHunk = (hunk & 0x80) !== 0x80; hunkId++; } return zlib.inflateSync(readFile(path, index + hunkId, size)).toString(); }