Explorar o código

enable parcel file watcher, expand parcel ignore patterns, replace fs watcher for git branches with parcel (#4805)

Aiden Cline hai 2 meses
pai
achega
99d7ff47c4

+ 10 - 0
packages/opencode/src/file/ignore.ts

@@ -6,6 +6,7 @@ export namespace FileIgnore {
     "bower_components",
     ".pnpm-store",
     "vendor",
+    ".npm",
     "dist",
     "build",
     "out",
@@ -22,12 +23,21 @@ export namespace FileIgnore {
     ".output",
     "desktop",
     ".sst",
+    ".cache",
+    ".webkit-cache",
+    "__pycache__",
+    ".pytest_cache",
+    "mypy_cache",
+    ".history",
+    ".gradle",
   ])
 
   const FILES = [
     "**/*.swp",
     "**/*.swo",
 
+    "**/*.pyc",
+
     // OS
     "**/.DS_Store",
     "**/Thumbs.db",

+ 33 - 19
packages/opencode/src/file/watcher.ts

@@ -1,6 +1,5 @@
 import z from "zod"
 import { Bus } from "../bus"
-import { Flag } from "../flag/flag"
 import { Instance } from "../project/instance"
 import { Log } from "../util/log"
 import { FileIgnore } from "./ignore"
@@ -8,6 +7,7 @@ import { Config } from "../config/config"
 // @ts-ignore
 import { createWrapper } from "@parcel/watcher/wrapper"
 import { lazy } from "@/util/lazy"
+import type ParcelWatcher from "@parcel/watcher"
 
 export namespace FileWatcher {
   const log = Log.create({ service: "file.watcher" })
@@ -44,32 +44,46 @@ export namespace FileWatcher {
         return {}
       }
       log.info("watcher backend", { platform: process.platform, backend })
-      const sub = await watcher().subscribe(
-        Instance.directory,
-        (err, evts) => {
-          if (err) return
-          for (const evt of evts) {
-            log.info("event", evt)
-            if (evt.type === "create") Bus.publish(Event.Updated, { file: evt.path, event: "add" })
-            if (evt.type === "update") Bus.publish(Event.Updated, { file: evt.path, event: "change" })
-            if (evt.type === "delete") Bus.publish(Event.Updated, { file: evt.path, event: "unlink" })
-          }
-        },
-        {
-          ignore: [...FileIgnore.PATTERNS, ...(cfg.watcher?.ignore ?? [])],
+      const subscribe: ParcelWatcher.SubscribeCallback = (err, evts) => {
+        if (err) return
+        for (const evt of evts) {
+          log.info("event", evt)
+          if (evt.type === "create") Bus.publish(Event.Updated, { file: evt.path, event: "add" })
+          if (evt.type === "update") Bus.publish(Event.Updated, { file: evt.path, event: "change" })
+          if (evt.type === "delete") Bus.publish(Event.Updated, { file: evt.path, event: "unlink" })
+        }
+      }
+
+      const subs = []
+      const cfgIgnores = cfg.watcher?.ignore ?? []
+
+      subs.push(
+        await watcher().subscribe(Instance.directory, subscribe, {
+          ignore: [...FileIgnore.PATTERNS, ...cfgIgnores],
           backend,
-        },
+        }),
       )
-      return { sub }
+
+      const vcsDir = Instance.project.vcsDir
+      if (vcsDir && !cfgIgnores.includes(".git") && !cfgIgnores.includes(vcsDir)) {
+        subs.push(
+          await watcher().subscribe(vcsDir, subscribe, {
+            ignore: ["hooks", "info", "logs", "objects", "refs", "worktrees", "modules", "lfs"],
+            backend,
+          }),
+        )
+      }
+
+      return { subs }
     },
     async (state) => {
-      if (!state.sub) return
-      await state.sub?.unsubscribe()
+      if (!state.subs) return
+      await Promise.all(state.subs.map((sub) => sub?.unsubscribe()))
     },
   )
 
   export function init() {
-    if (!Flag.OPENCODE_EXPERIMENTAL_WATCHER) return
+    // if (!Flag.OPENCODE_EXPERIMENTAL_WATCHER) return
     state()
   }
 }

+ 8 - 0
packages/opencode/src/project/project.ts

@@ -12,6 +12,7 @@ export namespace Project {
     .object({
       id: z.string(),
       worktree: z.string(),
+      vcsDir: z.string().optional(),
       vcs: z.literal("git").optional(),
       time: z.object({
         created: z.number(),
@@ -80,9 +81,16 @@ export namespace Project {
       .cwd(worktree)
       .text()
       .then((x) => x.trim())
+    const vcsDir = await $`git rev-parse --path-format=absolute --git-dir`
+      .quiet()
+      .nothrow()
+      .cwd(worktree)
+      .text()
+      .then((x) => x.trim())
     const project: Info = {
       id,
       worktree,
+      vcsDir,
       vcs: "git",
       time: {
         created: Date.now(),

+ 16 - 34
packages/opencode/src/project/vcs.ts

@@ -1,10 +1,10 @@
 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"
+import { FileWatcher } from "@/file/watcher"
 
 const log = Log.create({ service: "vcs" })
 
@@ -39,49 +39,31 @@ export namespace Vcs {
 
   const state = Instance.state(
     async () => {
-      if (Instance.project.vcs !== "git") {
-        return { branch: async () => undefined, watcher: undefined }
+      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 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.BranchUpdated, { branch: next })
-          }
-        })
-        log.info("watching", { path: gitHead })
-      } catch (e) {
-        log.warn("failed to watch git HEAD", { error: e })
-      }
+      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,
-        watcher,
+        unsubscribe,
       }
     },
     async (state) => {
-      state.watcher?.close()
+      state.unsubscribe?.()
     },
   )
 

+ 10 - 9
packages/sdk/js/src/gen/types.gen.ts

@@ -589,6 +589,14 @@ export type EventSessionError = {
   }
 }
 
+export type EventFileWatcherUpdated = {
+  type: "file.watcher.updated"
+  properties: {
+    file: string
+    event: "add" | "change" | "unlink"
+  }
+}
+
 export type EventVcsBranchUpdated = {
   type: "vcs.branch.updated"
   properties: {
@@ -647,14 +655,6 @@ export type EventServerConnected = {
   }
 }
 
-export type EventFileWatcherUpdated = {
-  type: "file.watcher.updated"
-  properties: {
-    file: string
-    event: "add" | "change" | "unlink"
-  }
-}
-
 export type Event =
   | EventInstallationUpdated
   | EventInstallationUpdateAvailable
@@ -677,12 +677,12 @@ export type Event =
   | EventSessionDeleted
   | EventSessionDiff
   | EventSessionError
+  | EventFileWatcherUpdated
   | EventVcsBranchUpdated
   | EventTuiPromptAppend
   | EventTuiCommandExecute
   | EventTuiToastShow
   | EventServerConnected
-  | EventFileWatcherUpdated
 
 export type GlobalEvent = {
   directory: string
@@ -692,6 +692,7 @@ export type GlobalEvent = {
 export type Project = {
   id: string
   worktree: string
+  vcsDir?: string
   vcs?: "git"
   time: {
     created: number