import Diff2html from "diff2html";
import fs from "fs/promises";
import { simpleGit } from "simple-git";
import { type FileNode } from "./files";
export interface Commit {
export const collectCommits = (item: { hash: any; message: any; body: any; author_name: any; author_email: any; date: any; refs: string; }) => ({
body: `${item.message}\n\n${item.body}`,
author_name: item.author_name,
author_email: item.author_email,
branches: item.refs.split(",").filter((ref: string) => !ref.includes("origin")).map((ref) => ref.trim()),
tags: item.refs.split(",").filter((ref: string) => ref.includes("tag: ")).map((ref) => ref.replace("tag: ", "")),
export const getCommits = async (repoPath: string): Promise<Commit[]> => {
const git = simpleGit(repoPath);
const commits = await git.log(["--branches", "--tags"]);
return commits.all.map(collectCommits);
export const getFiles = async (repoPath: string): Promise<FileNode[]> => {
const git = simpleGit(repoPath);
const paths = await git.raw(["ls-files"]);
for (const p of paths.split("\n").filter((p) => p.length > 0)) {
const stat = await fs.stat(`${repoPath}/${p}`);
const ext = path.extname(p).replace(".", "");
isDirectory: stat.isDirectory(),
absolutePath: `${repoPath}/${p}`,
// Ignore files not found
export const getFileHistory = async (repoPath: string, filePath: string): Promise<Commit[]> => {
const git = simpleGit(repoPath);
const stat = await fs.stat(`${repoPath}/${filePath}`);
const isLarge = stat.size > 1024 * 512;
const history = isLarge ? { all: [] } : await git.log([filePath]);
return history.all.map(collectCommits);
export const generateHTMLDiff = async (repoPath: string, hash: string): Promise<string> => {
const git = simpleGit(repoPath);
diff = await git.diff([`${hash}^..${hash}`]);
diff = await git.diff([hash]);
if (diff.length > 1024 * 512) {
return '<p>Diff too large to display.</p>';
const diffJson = Diff2html.parse(diff);
const diffHtml = Diff2html.html(diffJson, {