|
|
@@ -0,0 +1,86 @@
|
|
|
+import { $ } from "bun"
|
|
|
+import { watch, type FSWatcher } from "fs"
|
|
|
+import path from "path"
|
|
|
+import z from "zod"
|
|
|
+import { Log } from "@/util/log"
|
|
|
+import { Bus } from "@/bus"
|
|
|
+import { Instance } from "./instance"
|
|
|
+
|
|
|
+const log = Log.create({ service: "vcs" })
|
|
|
+
|
|
|
+export namespace Vcs {
|
|
|
+ export const Event = {
|
|
|
+ Changed: Bus.event(
|
|
|
+ "vcs.changed",
|
|
|
+ z.object({
|
|
|
+ branch: z.string().optional(),
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 () => {
|
|
|
+ if (Instance.project.vcs !== "git") {
|
|
|
+ return { branch: async () => undefined, watcher: undefined }
|
|
|
+ }
|
|
|
+ let current = await currentBranch()
|
|
|
+ log.info("initialized", { branch: current })
|
|
|
+
|
|
|
+ const gitDir = await $`git rev-parse --git-dir`
|
|
|
+ .quiet()
|
|
|
+ .nothrow()
|
|
|
+ .cwd(Instance.worktree)
|
|
|
+ .text()
|
|
|
+ .then((x) => x.trim())
|
|
|
+ .catch(() => undefined)
|
|
|
+ if (!gitDir) {
|
|
|
+ log.warn("failed to resolve git directory")
|
|
|
+ return { branch: async () => current, watcher: undefined }
|
|
|
+ }
|
|
|
+
|
|
|
+ const gitHead = path.join(gitDir, "HEAD")
|
|
|
+ let watcher: FSWatcher | undefined
|
|
|
+ // we should probably centralize file watching (see watcher.ts)
|
|
|
+ // but parcel still marked experimental rn
|
|
|
+ try {
|
|
|
+ watcher = watch(gitHead, async () => {
|
|
|
+ const next = await currentBranch()
|
|
|
+ if (next !== current) {
|
|
|
+ log.info("branch changed", { from: current, to: next })
|
|
|
+ current = next
|
|
|
+ Bus.publish(Event.Changed, { branch: next })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ log.info("watching", { path: gitHead })
|
|
|
+ } catch (e) {
|
|
|
+ log.warn("failed to watch git HEAD", { error: e })
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ branch: async () => current,
|
|
|
+ watcher,
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async (state) => {
|
|
|
+ state.watcher?.close()
|
|
|
+ },
|
|
|
+ )
|
|
|
+
|
|
|
+ export async function init() {
|
|
|
+ return state()
|
|
|
+ }
|
|
|
+
|
|
|
+ export async function branch() {
|
|
|
+ return await state().then((s) => s.branch())
|
|
|
+ }
|
|
|
+}
|