@wroud/git
Version:
A lightweight toolset for working with local git, including utilities for retrieving git commits and tags, ideal for CI/CD pipelines and automated release workflows.
137 lines • 5.32 kB
JavaScript
import { execa } from "execa";
import { v4 as uuidv4 } from "uuid";
import { validateGitEnvironment } from "./validateGitEnvironment.js";
const linesPerCommit = 6;
const trailerRegex = /^(?<token>[^\s\t\n]+)[\s\t]*(?::[\s\t]*(.+)|(#[^\s\n]+.+))$/;
const trailerMultilineValueRegex = /^[\s\t]{1}(?<value>.*)/gm;
const tagsRegex = /tag: ([^,]+)[,)]/g;
export async function* getGitCommits({ from, to = "HEAD", path = ".", maxCommits, includeTags = true, includeTrailers = true, customTrailers = [], customLinks = [], } = {}) {
await validateGitEnvironment();
/**
* %h: abbreviated commit hash
* %d: ref names
* %an: author name
* %ae: author email
* %s: subject
* %b: body
* %n: newline
*/
const endMarker = `===${uuidv4()}===`;
const args = [
"--no-pager",
"log",
`${from ? `${from}...` : ""}${to}`,
`--pretty=format:%h%n%d%n%an%n%ae%n%s%n%b%n${endMarker}`,
];
if (path) {
args.push("--", path);
}
const max = maxCommits ?? Infinity;
let commitCount = 0;
let commitBuffer = [];
for await (const line of execa("git", args)) {
if (line === endMarker) {
if (commitBuffer.length < linesPerCommit) {
console.warn("Incomplete commit information encountered.");
commitBuffer = [];
continue;
}
const [hash, ref, authorName, authorEmail, subject, ...bodyLines] = commitBuffer;
let bodyLinesCopy = [...bodyLines];
const trailers = [];
if (includeTrailers) {
let currentTrailer = null;
let canStartTrailer = false;
let lineIndex = 0;
for (const line of bodyLines) {
if (line === "") {
canStartTrailer = true;
}
let foundTrailer = null;
for (const customTrailer of customTrailers) {
const match = line.match(customTrailer);
if (match) {
const token = match.groups["token"];
const value = match.groups["value"];
foundTrailer = { token, value };
break;
}
}
if (!foundTrailer) {
const match = line.match(trailerRegex);
if (match) {
const token = match[1];
const value = (match[2] || match[3]);
foundTrailer = { token, value };
}
}
if (canStartTrailer && foundTrailer) {
if (currentTrailer) {
trailers.push(currentTrailer);
}
currentTrailer = foundTrailer;
}
else {
if (currentTrailer) {
const match = line.match(trailerMultilineValueRegex);
if (match) {
currentTrailer.value += "\n" + match.groups["value"];
}
else {
trailers.push(currentTrailer);
currentTrailer = null;
}
}
}
if (bodyLinesCopy.length >= lineIndex &&
(currentTrailer || trailers.length > 0)) {
bodyLinesCopy.splice(lineIndex);
}
lineIndex++;
}
if (currentTrailer) {
trailers.push(currentTrailer);
}
}
const links = {};
for (const link of customLinks) {
for (const line of [subject, ...bodyLines]) {
if (line) {
const matches = line.matchAll(link);
for (const match of matches) {
const token = match.groups?.["token"];
links[token] = {
token,
link: match.groups?.["link"],
...match.groups,
};
}
}
}
}
const commitInfo = {
hash: hash,
tags: [],
authorName: authorName,
authorEmail: authorEmail,
subject: subject,
body: bodyLinesCopy.join("\n"),
trailers,
links,
};
if (includeTags) {
commitInfo.tags = Array.from(ref.matchAll(tagsRegex)).map((match) => match[1]);
}
yield commitInfo;
commitBuffer = [];
commitCount++;
if (commitCount >= max) {
break;
}
}
else {
commitBuffer.push(line);
}
}
}
//# sourceMappingURL=getGitCommits.js.map