UNPKG

git-blame-json

Version:

Execute git blame async and efficiently with results as Map

82 lines 3.53 kB
"use strict"; /* eslint-disable @typescript-eslint/no-non-null-assertion */ /** * Spawns git blame and parses results into JSON, via stream (so, no problem on huge files) */ Object.defineProperty(exports, "__esModule", { value: true }); const child_process_1 = require("child_process"); const readline_1 = require("readline"); const camel_case_1 = require("camel-case"); async function blame(filename, options = {}, gitPath = 'git') { var _a, _b, _c, _d; /** * @see {@link https://git-scm.com/docs/git-blame#_options} */ const args = ['--no-pager', 'blame', '--line-porcelain']; if (typeof options.workTree === 'string') { args.unshift(`--work-tree=${options.workTree}`); } if (typeof options.gitDir === 'string') { args.unshift(`--git-dir=${options.gitDir}`); } if (typeof options.ignoreWhitespace === 'boolean') { args.push('-w'); } if (typeof options.range === 'string') { args.push(`-L${options.range}`); } if (typeof options.rev === 'string') { args.push(options.rev); } const git = child_process_1.spawn(gitPath, [...args, '--', filename], { windowsHide: true, }); const readline = readline_1.createInterface({ input: git.stdout }); let currentLine; const linesMap = new Map(); for await (const line of readline) { // https://git-scm.com/docs/git-blame#_the_porcelain_format // Each blame entry always starts with a line of: // <40-byte hex sha1> <sourceline> <resultline> <num_lines> // like: 49790775624c422f67057f7bb936f35df920e391 94 120 3 const parsedLine = /^(?<hash>[a-f0-9]{40,40})\s(?<sourceline>\d+)\s(?<resultLine>\d+)\s(?<numLines>\d+)$/.exec(line); if ((_a = parsedLine) === null || _a === void 0 ? void 0 : _a.groups) { // this is a new line info const sourceLine = parseInt(parsedLine.groups.sourceline, 10); const resultLine = parseInt((_b = parsedLine) === null || _b === void 0 ? void 0 : _b.groups.resultLine, 10); const numberOfLines = parseInt((_c = parsedLine) === null || _c === void 0 ? void 0 : _c.groups.numLines, 10); currentLine = { hash: parsedLine.groups.hash, sourceLine, resultLine, numberOfLines, }; // set for all lines for (let i = resultLine; i < resultLine + numberOfLines; i++) linesMap.set(i, currentLine); } else { if (currentLine) { const commitInfo = /^(?<token>[a-z]+(-(?<subtoken>[a-z]+))?)\s(?<data>.+)$/.exec(line); if ((_d = commitInfo) === null || _d === void 0 ? void 0 : _d.groups) { const property = camel_case_1.camelCase(commitInfo.groups.token); let value = commitInfo.groups.data; switch (commitInfo.groups.subtoken) { case 'mail': // remove <> from email value = value.slice(1, -1); break; case 'time': // parse datestamp into number value = parseInt(value, 10); break; } currentLine[property] = value; } } } } return linesMap; } exports.blame = blame; //# sourceMappingURL=index.js.map