vcs.ts 1.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. import { $ } from "bun"
  2. import path from "path"
  3. import z from "zod"
  4. import { Log } from "@/util/log"
  5. import { Bus } from "@/bus"
  6. import { Instance } from "./instance"
  7. import { FileWatcher } from "@/file/watcher"
  8. const log = Log.create({ service: "vcs" })
  9. export namespace Vcs {
  10. export const Event = {
  11. BranchUpdated: Bus.event(
  12. "vcs.branch.updated",
  13. z.object({
  14. branch: z.string().optional(),
  15. }),
  16. ),
  17. }
  18. export const Info = z
  19. .object({
  20. branch: z.string(),
  21. })
  22. .meta({
  23. ref: "VcsInfo",
  24. })
  25. export type Info = z.infer<typeof Info>
  26. async function currentBranch() {
  27. return $`git rev-parse --abbrev-ref HEAD`
  28. .quiet()
  29. .nothrow()
  30. .cwd(Instance.worktree)
  31. .text()
  32. .then((x) => x.trim())
  33. .catch(() => undefined)
  34. }
  35. const state = Instance.state(
  36. async () => {
  37. const vcsDir = Instance.project.vcsDir
  38. if (Instance.project.vcs !== "git" || !vcsDir) {
  39. return { branch: async () => undefined, unsubscribe: undefined }
  40. }
  41. let current = await currentBranch()
  42. log.info("initialized", { branch: current })
  43. const head = path.join(vcsDir, "HEAD")
  44. const unsubscribe = Bus.subscribe(FileWatcher.Event.Updated, async (evt) => {
  45. if (evt.properties.file !== head) return
  46. const next = await currentBranch()
  47. if (next !== current) {
  48. log.info("branch changed", { from: current, to: next })
  49. current = next
  50. Bus.publish(Event.BranchUpdated, { branch: next })
  51. }
  52. })
  53. return {
  54. branch: async () => current,
  55. unsubscribe,
  56. }
  57. },
  58. async (state) => {
  59. state.unsubscribe?.()
  60. },
  61. )
  62. export async function init() {
  63. return state()
  64. }
  65. export async function branch() {
  66. return await state().then((s) => s.branch())
  67. }
  68. }