summaryrefslogtreecommitdiffhomepage
path: root/packages/opencode/src/project/vcs.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/opencode/src/project/vcs.ts')
-rw-r--r--packages/opencode/src/project/vcs.ts86
1 files changed, 86 insertions, 0 deletions
diff --git a/packages/opencode/src/project/vcs.ts b/packages/opencode/src/project/vcs.ts
new file mode 100644
index 000000000..153583564
--- /dev/null
+++ b/packages/opencode/src/project/vcs.ts
@@ -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())
+ }
+}