diff --git a/apps/host-daemon/src/command-handlers/host-branches.ts b/apps/host-daemon/src/command-handlers/host-branches.ts index 0f45e65ac..c246cc922 100644 --- a/apps/host-daemon/src/command-handlers/host-branches.ts +++ b/apps/host-daemon/src/command-handlers/host-branches.ts @@ -1,7 +1,7 @@ import path from "node:path"; import type { GitBranchRefClassification } from "@bb/domain"; import { - detectGitRepo, + detectGitSource, fetchRemoteBranches, getCheckoutRef, getGitCommonDir, @@ -137,7 +137,7 @@ export async function listHostBranches( throw new CommandDispatchError("invalid_path", "Path must be absolute"); } - if (!(await detectGitRepo(command.path))) { + if (!(await detectGitSource(command.path))) { return { branches: [], branchesTruncated: false, diff --git a/packages/host-workspace/src/git.ts b/packages/host-workspace/src/git.ts index c60fe6dd1..7f5d204dc 100644 --- a/packages/host-workspace/src/git.ts +++ b/packages/host-workspace/src/git.ts @@ -587,23 +587,58 @@ async function findWorkspaceGitOperationMarker( return undefined; } +export type GitRepoKind = "work-tree" | "bare" | "none"; + +/** + * Classifies `cwd`: inside a work tree, at/inside a bare repository (for + * example the "bare clone + sibling worktrees" layout where `/.git` + * is a gitdir file pointing at `/.bare`), or not a repository at all. + */ +export async function detectGitRepoKind( + cwd: string, + options: GitTimeoutOptions = {}, +): Promise { + const result = await runGit( + ["rev-parse", "--is-inside-work-tree", "--is-bare-repository"], + { cwd, allowFailure: true, timeoutMs: options.timeoutMs }, + ); + if (result.exitCode !== 0) { + return "none"; + } + const [insideWorkTree, bare] = trimOutput(result.stdout).split("\n"); + if (insideWorkTree === "true") { + return "work-tree"; + } + if (bare === "true") { + return "bare"; + } + return "none"; +} + +/** True when `cwd` is inside a git work tree (a checkout). */ export async function detectGitRepo( cwd: string, options: GitTimeoutOptions = {}, ): Promise { - const result = await runGit(["rev-parse", "--is-inside-work-tree"], { - cwd, - allowFailure: true, - timeoutMs: options.timeoutMs, - }); - return result.exitCode === 0 && trimOutput(result.stdout) === "true"; + return (await detectGitRepoKind(cwd, options)) === "work-tree"; +} + +/** + * True when `cwd` is a git repository that can serve as the source of a + * worktree: a checkout or a bare repository. + */ +export async function detectGitSource( + cwd: string, + options: GitTimeoutOptions = {}, +): Promise { + return (await detectGitRepoKind(cwd, options)) !== "none"; } export async function ensureGitRepo( cwd: string, options: GitTimeoutOptions = {}, ): Promise { - if (await detectGitRepo(cwd, options)) { + if (await detectGitSource(cwd, options)) { return; } @@ -619,7 +654,7 @@ export async function readGitRepositoryState( cwd: string, options: GitTimeoutOptions = {}, ): Promise { - if (!(await detectGitRepo(cwd, options))) { + if (!(await detectGitSource(cwd, options))) { return "not_git"; } const result = await runGit(["rev-list", "--all", "--max-count=1"], { @@ -671,7 +706,7 @@ export async function getCheckoutRef( cwd: string, options: GitTimeoutOptions = {}, ): Promise { - if (!(await detectGitRepo(cwd, options))) { + if (!(await detectGitSource(cwd, options))) { return { kind: "unknown", reason: "Path is not a git repository" }; } @@ -867,6 +902,9 @@ export async function getWorkspaceGitOperation( cwd: string, ): Promise { await ensureGitRepo(cwd); + if ((await detectGitRepoKind(cwd)) === "bare") { + return { kind: "none" }; + } const [gitDir, status] = await Promise.all([ getAbsoluteGitDir(cwd), @@ -1467,6 +1505,10 @@ export async function listRemoteBranches(cwd: string): Promise { export async function hasUncommittedChanges(cwd: string): Promise { await ensureGitRepo(cwd); + if ((await detectGitRepoKind(cwd)) === "bare") { + // A bare repository has no index or work tree to be dirty. + return false; + } const status = await runGit( ["--no-optional-locks", "status", "--porcelain=v1", "--untracked-files=all"], { cwd }, diff --git a/packages/host-workspace/src/index.ts b/packages/host-workspace/src/index.ts index cb9c4a522..2c43d292d 100644 --- a/packages/host-workspace/src/index.ts +++ b/packages/host-workspace/src/index.ts @@ -30,6 +30,8 @@ export type { export { WorkspaceError, detectGitRepo, + detectGitRepoKind, + detectGitSource, fetchRemoteBranches, getCheckoutRef, getCurrentBranch,