import { $ } from "bun" import path from "path" import z from "zod" import { Log } from "@/util/log" import { Bus } from "@/bus" import { Instance } from "./instance" import { FileWatcher } from "@/file/watcher" const log = Log.create({ service: "vcs" }) export namespace Vcs { export const Event = { BranchUpdated: Bus.event( "vcs.branch.updated", z.object({ branch: z.string().optional(), }), ), } export const Info = z .object({ branch: z.string(), }) .meta({ ref: "VcsInfo", }) export type Info = z.infer async function currentBranch() { return $`git rev-parse --abbrev-ref HEAD` .quiet() .nothrow() .cwd(Instance.worktree) .text() .then((x) => x.trim()) .catch(() => undefined) } const state = Instance.state( async () => { const vcsDir = Instance.project.vcsDir if (Instance.project.vcs !== "git" || !vcsDir) { return { branch: async () => undefined, unsubscribe: undefined } } let current = await currentBranch() log.info("initialized", { branch: current }) const head = path.join(vcsDir, "HEAD") const unsubscribe = Bus.subscribe(FileWatcher.Event.Updated, async (evt) => { if (evt.properties.file !== head) return const next = await currentBranch() if (next !== current) { log.info("branch changed", { from: current, to: next }) current = next Bus.publish(Event.BranchUpdated, { branch: next }) } }) return { branch: async () => current, unsubscribe, } }, async (state) => { state.unsubscribe?.() }, ) export async function init() { return state() } export async function branch() { return await state().then((s) => s.branch()) } }