import { defined, get } from "../../utils/anonymous.ts"; import { type MaybeIterable, surelyIterable } from "../../utils/iterator.ts"; import { gitDir } from "./index.ts"; import type { Commit, CommitFile } from "./types.ts"; const format = [ "H", "h", "aI", "aN", "aE", "cI", "cN", "cE", // "G?", "GS", "GK", "GF", "GG", ]; export async function getLastCommitForOneOfFiles( sources: MaybeIterable, ): Promise { const files = surelyIterable(sources); const gitLogs = (await Promise.all( Iterator.from(files).map(async ({ pathname }) => { const gitLog = new Deno.Command("git", { args: [ "log", "--follow", "-1", `--pretty=format:${format.map((x) => `%${x}`).join("%n")}`, "--", pathname, ], }); const { stdout } = await gitLog.output(); const result = new TextDecoder().decode(stdout).trim(); if (result.length <= 0) { return undefined; } const [ hash, abbrHash, authorDate, authorName, authorEmail, committerDate, committerName, committerEmail, // signatureValidation, signer, key, keyFingerPrint, ...rawLines ] = result.split("\n"); const raw = rawLines.join("\n").trim(); const commit: Commit = { // deno-lint-ignore no-undef files: await fileStatusFromCommit(hash, Iterator.from(files)), hash: { long: hash, short: abbrHash }, author: { date: new Date(authorDate), name: authorName, email: authorEmail, }, committer: { date: new Date(committerDate), name: committerName, email: committerEmail, }, }; if (raw.length > 0) { commit.signature = { type: raw.startsWith("gpgsm:") ? "x509" : raw.startsWith("gpg:") ? "gpg" : "ssh", signer, key: { long: keyFingerPrint, short: key }, rawMessage: raw, }; } return commit; }), )).filter(defined); const last = gitLogs.sort(({ committer: a }, { committer: b }) => b.date.getTime() - a.date.getTime() )?.[0]; if (last === undefined) return undefined; const final = gitLogs.filter(({ hash }) => hash.long === last.hash.long); last.files = final.flatMap(get("files")); return last; } async function fileStatusFromCommit( hash: string, files: Iterable, ): Promise { const gitDiffTree = new Deno.Command("git", { args: [ "diff-tree", "--no-commit-id", "--name-status", "-r", hash, ], }); const { stdout } = await gitDiffTree.output(); const result = new TextDecoder().decode(stdout).trim().split("\n").filter( defined, ); const dir = await gitDir(); return result.map((line) => { const [status, path] = line.split("\t"); if ( // deno-lint-ignore no-undef Iterator.from(files).some((file) => file.pathname.replace(dir.pathname, "").includes(path) ) ) { return { path: new URL(path, dir), status: status === "A" ? "added" : status === "D" ? "deleted" : "modified", } as const; } return undefined; }).filter(defined); } export async function fileCreationCommitDate( file: URL, ): Promise { const gitDiffTree = new Deno.Command("git", { args: [ "log", "--follow", "--diff-filter=A", "--format=%cI", "--", file.pathname, ], }); const { stdout } = await gitDiffTree.output(); try { return new Date(new TextDecoder().decode(stdout).trim()); } catch { return undefined; } }