From f9349f374c683b78f953126286cd7b1de8f5a072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sun, 19 Apr 2026 16:51:44 -0400 Subject: [PATCH] Detect git workspaces with rev-parse instead of a .git stat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `isGitRepo` only accepted a `.git` directory, so linked worktrees and submodules — both of which store `.git` as a file containing `gitdir: …` — silently fell into the non-git branch. The baseline diff then replaced `git status` as the source of truth for workspaces that git itself recognises, and edge cases where the baseline is missing (a freshly-created worktree mid-run, a submodule without a captured snapshot) bled into `no_baseline` skips instead of running the authoritative diff. Call `git rev-parse --is-inside-work-tree` and take git's own yes/no answer. Exit 0 means git would happily run `status` there; any other outcome (no git binary, corrupted ref, genuinely non-git) lands us on the baseline path exactly as before. --- src/main/agent-runtime/artifact-projector.ts | 23 ++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/agent-runtime/artifact-projector.ts b/src/main/agent-runtime/artifact-projector.ts index 7cb574bc..9fa45411 100644 --- a/src/main/agent-runtime/artifact-projector.ts +++ b/src/main/agent-runtime/artifact-projector.ts @@ -1,6 +1,6 @@ import { isAbsolute, relative, resolve } from "node:path"; import { execFileSync } from "node:child_process"; -import { existsSync, readdirSync, statSync } from "node:fs"; +import { readdirSync, statSync } from "node:fs"; import type { AppDatabase } from "@main/database"; /** @@ -392,9 +392,28 @@ function projectBashArtifacts( return projectChangedPaths(db, ctx, change.paths); } +/** + * Ask git whether `workspacePath` sits inside a working tree. This + * covers the three forms the `.git` entry takes in real setups: + * + * - a directory (a plain clone), + * - a file with `gitdir: …` (a linked worktree), or + * - a file with `gitdir: …/modules/…` (a submodule). + * + * The previous `existsSync + isDirectory` probe only recognised the + * first form, so linked worktrees and submodules silently degraded + * onto the baseline path — and lost `git status` as the authoritative + * diff source. `rev-parse --is-inside-work-tree` exits 0 whenever git + * itself would accept the directory, which is the semantics we want. + */ function isGitRepo(workspacePath: string): boolean { try { - return existsSync(resolve(workspacePath, ".git")) && statSync(resolve(workspacePath, ".git")).isDirectory(); + execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { + cwd: workspacePath, + timeout: 2_000, + stdio: ["ignore", "ignore", "ignore"], + }); + return true; } catch { return false; }