git-blame-json
Version:
Execute git blame async and efficiently with results as Map
82 lines • 3.53 kB
JavaScript
;
/* 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