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 { Env } from "../../env";
import type { RepositoryHead } from "../../types";
import type { RepositoryServiceDeps } from "./types";
const GIT_TREE_ID_REGEXP = /tree[\s]+(.*)\n/im;
const GIT_PARENT_ID_REGEXP = /parent[\s]+(.*)\n/im;
const GIT_AUTHOR_REGEXP =
/author[\s]+(.*)[\s]+<(.*)>[\s]+([\d]+)[\s]+([\+\d]+)\n/im;
const GIT_COMMITTER_REGEXP =
/committer[\s]+(.*)[\s]+<(.*)>[\s]+([\d]+)[\s]+([\+\d]+)\n/im;
const GIT_COMMITT_MESSAGE_REGEXP =
/committer[\s]+.*[\s]+<.*>[\s]+[\d]+[\s]+[\+\d]+\n\n(.*)\n/im;
const makeGetRepositoryHead: ServiceMethodFactory<
RepositoryServiceDeps,
[Repository, string | undefined],
Promise<RepositoryHead>
> = ({ request }) => {
return async (repo, ref = "HEAD") => {
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}`);
}
const gitCatFileProcess = spawn("git", ["cat-file", "-p", ref], {
cwd: repoPath,
});
const gitCatFileResult = await new Promise<string>((resolve, reject) => {
let buffer = [] as string[];
gitCatFileProcess.stdout.on("data", (data) => buffer.push(data));
gitCatFileProcess.stderr.on("data", (data) => {
reject(new Error(Buffer.from(data).toString("utf-8")));
});
gitCatFileProcess.stdout.on("close", () => {
resolve(buffer.join(""));
});
});
const treeIdMatches = GIT_TREE_ID_REGEXP.exec(gitCatFileResult);
const parentIdMatches = GIT_PARENT_ID_REGEXP.exec(gitCatFileResult);
const authorMatches = GIT_AUTHOR_REGEXP.exec(gitCatFileResult);
const committerMatches = GIT_COMMITTER_REGEXP.exec(gitCatFileResult);
const commitMessageMatches =
GIT_COMMITT_MESSAGE_REGEXP.exec(gitCatFileResult);
let treeId: null | string = null;
if (treeIdMatches != null || Array.isArray(treeIdMatches)) {
treeId = treeIdMatches[1];
}
let parentId: null | string = null;
if (parentIdMatches != null && Array.isArray(parentIdMatches)) {
parentId = parentIdMatches[1];
}
if (authorMatches == null || Array.isArray(authorMatches) === false) {
throw new Error("Invalid HEAD, missing author informations.");
}
const [_, authorName, authorEmail, authorTimestamp, authorTimezone] =
authorMatches;
if (committerMatches == null || Array.isArray(committerMatches) === false) {
throw new Error("Invalid HEAD, missing committer informations.");
}
const [
__,
committerName,
committerEmail,
committerTimestamp,
committerTimezone,
] = committerMatches;
if (
commitMessageMatches == null ||
Array.isArray(commitMessageMatches) === false
) {
throw new Error("Invalid HEAD, missing commit message.");
}
const commitMessage = commitMessageMatches[1];
return {
treeId,
parentId,
author: {
name: authorName,
email: authorEmail,
timestamp: parseInt(authorTimestamp, 10),
timezone: authorTimezone,
},
committer: {
name: committerName,
email: committerEmail,
timestamp: parseInt(committerTimestamp, 10),
timezone: committerTimezone,
},
commitMessage,
} as RepositoryHead;
};
};
export default makeGetRepositoryHead;