import { existsSync } from "node:fs";
import { spawn } from "node:child_process";
import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
import type { Repository } from "@prisma/client";
import type { RepositoryLog } from "../../types";
import { Const } from "../../const";
import { Env } from "../../env";
import type { RepositoryServiceDeps } from "./types";
const GIT_LOG_NEWLINEW_FINDER_REGEXP =
/ \^@\^[a-z_]+\^@\^: \^@\^([^\^]+|)\^@\^,?/gim;
const makeGetRepositoryCommitLog: ServiceMethodFactory<
RepositoryServiceDeps,
[Repository, string | undefined, string | undefined, boolean | undefined],
Promise<RepositoryLog[]>
> = ({ request }) => {
return async (
repo,
path = "",
ref = Const.DEFAULT_HEAD_REF,
onlyLast = false
) => {
const parentOrg = await request.prisma.organization.findUnique({
where: {
id: repo.organizationId,
},
});
if (parentOrg == null) {
throw new Error(
`Could not find the parent organization for project "${repo.slug}".`
);
}
const repoPath = `${Env.GIT_REPOSITORIES_ROOT}/${parentOrg.slug}/${repo.slug}.git`;
if (existsSync(repoPath) === false) {
throw new Error(`Could not find a valid git repository at: ${repoPath}`);
}
var format =
"{%n ^@^commit^@^: ^@^%H^@^,%n ^@^abbreviated_commit^@^: ^@^%h^@^,%n ^@^tree^@^: ^@^%T^@^,%n ^@^abbreviated_tree^@^: ^@^%t^@^,%n ^@^parent^@^: ^@^%P^@^,%n ^@^abbreviated_parent^@^: ^@^%p^@^,%n ^@^refs^@^: ^@^%D^@^,%n ^@^encoding^@^: ^@^%e^@^,%n ^@^subject^@^: ^@^%s^@^,%n ^@^sanitized_subject_line^@^: ^@^%f^@^,%n ^@^body^@^: ^@^%b^@^,%n ^@^commit_notes^@^: ^@^%N^@^,%n ^@^verification_flag^@^: ^@^%G?^@^,%n ^@^signer^@^: ^@^%GS^@^,%n ^@^signer_key^@^: ^@^%GK^@^,%n ^@^author^@^: {%n ^@^name^@^: ^@^%aN^@^,%n ^@^email^@^: ^@^%aE^@^,%n ^@^date^@^: ^@^%aD^@^%n },%n ^@^commiter^@^: {%n ^@^name^@^: ^@^%cN^@^,%n ^@^email^@^: ^@^%cE^@^,%n ^@^date^@^: ^@^%cD^@^%n }%n},";
const args = [
"log",
"--quiet",
`--pretty=format:${format}`,
onlyLast ? "-1" : null,
ref,
path != null && path.trim() !== "" ? "--" : null,
path != null && path.trim() !== "" ? "-p" : null,
path != null && path.trim() !== "" ? `${path}` : null,
].filter((x): x is string => x != null);
try {
const gitLogProcess = spawn("git", args, {
cwd: repoPath,
env: {
LANG: "C",
},
});
const gitLogResult = await new Promise<RepositoryLog[]>(
(resolve, reject) => {
let buffer = [] as string[];
gitLogProcess.stdout.on("data", (data) => buffer.push(data));
gitLogProcess.stderr.on("data", (data) => {
reject(new Error(Buffer.from(data).toString("utf-8")));
});
gitLogProcess.stdout.on("close", () => {
let escapedJson = buffer.join("");
const fieldsToEscape = escapedJson.match(
GIT_LOG_NEWLINEW_FINDER_REGEXP
);
if (fieldsToEscape != null && Array.isArray(fieldsToEscape)) {
fieldsToEscape.forEach((fieldTxt) => {
escapedJson = escapedJson.replace(
fieldTxt,
fieldTxt.split("\n").join("\\n")
);
});
}
escapedJson = escapedJson
.replace(/"/gm, '\\"')
.replace(/\^@\^/gm, '"');
try {
resolve(
JSON.parse(
`[${escapedJson.substring(0, escapedJson.length - 1)}]`
)
);
} catch (err) {
resolve([]);
}
});
}
);
return gitLogResult as RepositoryLog[];
} catch (err) {
console.error("Cannot get git log", err);
return [];
}
};
};
export default makeGetRepositoryCommitLog;