GitFOSS
fe04f67 (parent 5c6496d)5/9/2026, 8:25:56 AM
.ts
TypeScript
(application/typescript)
// std
import { existsSync } from "node:fs";
import { spawn } from "node:child_process";
// 1st-party
import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
// generated via script[generate:prisma]
import type { Repository } from "@prisma/client";
// app
import type { RepositoryHead } from "../../types";
import { Const } from "../../const";
import { Env } from "../../env";
// service
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 = Const.DEFAULT_HEAD_REF) => {
    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}`);
    }

    if (ref === Const.DEFAULT_HEAD_REF) {
      ref = Const.PRIMARY_BRANCH_REF;
    }

    const gitCatFileProcess = spawn("git", ["cat-file", "-p", ref], {
      cwd: repoPath,
      env: {
        LANG: "C",
      },
    });

    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;

    let commitMessage: string = "<missing commit message>";
    if (commitMessageMatches != null && Array.isArray(commitMessageMatches)) {
      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;