import { jsonGet } from "./_request"; import { splitMessage, type CommitSummary } from "./repo"; import type { Did } from "@atcute/lexicons/syntax"; import type { BobbinContext, XrpcRequestInit } from "./client"; import type { Revspec } from "./pullRoute"; import type * as ShTangledGitDefs from "./lexicons/types/sh/tangled/git/defs"; import type * as GetDiff from "./lexicons/types/sh/tangled/git/temp2/getDiff"; import type * as GetInterdiff from "./lexicons/types/sh/tangled/git/temp2/getInterdiff"; import type * as ListCommits from "./lexicons/types/sh/tangled/git/temp2/listCommits"; import type * as MergeCheck from "./lexicons/types/sh/tangled/git/temp2/mergeCheck"; // gitmirror's own xrpc surface, proxied through bobbin — its grpc service is // internal and streaming, so the read paths the frontend needs live here const LIST_COMMITS_NSID = "sh.tangled.git.temp2.listCommits"; const GET_DIFF_NSID = "sh.tangled.git.temp2.getDiff"; const GET_INTERDIFF_NSID = "sh.tangled.git.temp2.getInterdiff"; const MERGE_CHECK_NSID = "sh.tangled.git.temp2.mergeCheck"; export type MirrorCommit = ListCommits.Commit; export const listCommits = ( ctx: BobbinContext, params: ListCommits.$params, init?: XrpcRequestInit ) => jsonGet(ctx, LIST_COMMITS_NSID, params, init); const LOG_LIMIT = 100; // every commit a pull version brought, newest first export const listLog = async ( ctx: BobbinContext, repo: Did, range: Revspec, init?: XrpcRequestInit ): Promise => { const page = await listCommits( ctx, { repo, ranges: [`${range.base}..${range.head}`], limit: LOG_LIMIT }, init ); return page.commits.map(toMirrorCommitSummary); }; export type MirrorFileDiff = ShTangledGitDefs.FileDiff; export const getDiff = (ctx: BobbinContext, params: GetDiff.$params, init?: XrpcRequestInit) => jsonGet(ctx, GET_DIFF_NSID, params, init); // `base*` is the older version's span, `head*` the newer one's: gitmirror // rebases base1..base2 onto head1 and diffs the result against head2 export const getInterdiff = ( ctx: BobbinContext, params: GetInterdiff.$params, init?: XrpcRequestInit ) => jsonGet(ctx, GET_INTERDIFF_NSID, params, init); export type MirrorConflict = MergeCheck.Conflict; export const mergeCheck = ( ctx: BobbinContext, params: MergeCheck.$params, init?: XrpcRequestInit ) => jsonGet(ctx, MERGE_CHECK_NSID, params, init); export type MirrorFileKind = "new" | "deleted" | "renamed" | "changed"; // an add or delete carries the same path on both sides — gix names both resources // after the change's location — so the side that doesn't exist is the one whose oid // is null, not the one whose path is empty export const diffSideExists = (src: ShTangledGitDefs.DiffSrc): boolean => src.path !== "" && !/^0+$/.test(src.oid); export const diffFileName = (file: MirrorFileDiff): string => file.rhsSrc.path || file.lhsSrc.path; export const diffFileKind = (file: MirrorFileDiff): MirrorFileKind => { if (!diffSideExists(file.lhsSrc)) return "new"; if (!diffSideExists(file.rhsSrc)) return "deleted"; return file.lhsSrc.path === file.rhsSrc.path ? "changed" : "renamed"; }; // the payload has no line text, only aligned line numbers: a pair with one side // missing is a pure insertion or deletion, a pair with both is a replaced line export const diffFileStat = (file: MirrorFileDiff): { insertions: number; deletions: number } => { let insertions = 0; let deletions = 0; for (const hunk of file.hunks) { for (const line of hunk.lines) { if (line.rhs !== undefined) insertions++; if (line.lhs !== undefined) deletions++; } } return { insertions, deletions }; }; export const toMirrorCommitSummary = (commit: MirrorCommit): CommitSummary => { const [subject, body] = splitMessage(commit.message); return { hash: commit.oid, shortHash: commit.oid.slice(0, 8), subject, body, authorName: commit.author.name, authorEmail: commit.author.email, when: commit.committer.when ?? commit.author.when, // the pull page links a commit row as `firstParent..commit` parent: commit.parents[0], // jj stashes the change id in a commit header, same lookup the appview does changeId: commit.extraHeaders.find((header) => header.key === "change-id")?.value }; };