diff --git a/packages/coding-agent/src/core/footer-data-provider.ts b/packages/coding-agent/src/core/footer-data-provider.ts index 3119d63d35a7..1fe1d3915caf 100644 --- a/packages/coding-agent/src/core/footer-data-provider.ts +++ b/packages/coding-agent/src/core/footer-data-provider.ts @@ -2,6 +2,7 @@ import { type ExecFileException, execFile, spawnSync } from "child_process"; import { existsSync, type FSWatcher, readFileSync, type Stats, statSync, unwatchFile, watchFile } from "fs"; import { dirname, join, resolve } from "path"; import { closeWatcher, FS_WATCH_RETRY_DELAY_MS, watchWithErrorHandler } from "../utils/fs-watch.ts"; +import { linkedWorktreeName } from "./git-worktree.ts"; export type GitPaths = { repoDir: string; @@ -131,6 +132,11 @@ export class FooterDataProvider { return this.cachedBranch; } + /** Linked worktree name, null in the main checkout or outside a repo */ + getWorktreeName(): string | null { + return this.gitPaths ? linkedWorktreeName(this.gitPaths) : null; + } + /** Extension status texts set via ctx.ui.setStatus() */ getExtensionStatuses(): ReadonlyMap { return this.extensionStatuses; @@ -384,5 +390,5 @@ export class FooterDataProvider { /** Read-only view for extensions - excludes setExtensionStatus, setAvailableProviderCount and dispose */ export type ReadonlyFooterDataProvider = Pick< FooterDataProvider, - "getGitBranch" | "getExtensionStatuses" | "getAvailableProviderCount" | "onBranchChange" + "getGitBranch" | "getWorktreeName" | "getExtensionStatuses" | "getAvailableProviderCount" | "onBranchChange" >; diff --git a/packages/coding-agent/src/core/git-worktree.ts b/packages/coding-agent/src/core/git-worktree.ts new file mode 100644 index 000000000000..3111de864e58 --- /dev/null +++ b/packages/coding-agent/src/core/git-worktree.ts @@ -0,0 +1,12 @@ +import { basename, dirname, resolve } from "path"; +import type { GitPaths } from "./footer-data-provider.ts"; + +/** + * Name of the linked worktree that `paths` belongs to, or null for the main checkout. + * A linked worktree's HEAD lives under `/worktrees//`, apart from the shared git dir. + */ +export function linkedWorktreeName(paths: GitPaths): string | null { + const gitDir = resolve(dirname(paths.headPath)); + if (gitDir === resolve(paths.commonGitDir)) return null; + return basename(paths.repoDir); +} diff --git a/packages/coding-agent/test/footer-width.test.ts b/packages/coding-agent/test/footer-width.test.ts index 12c074d01075..e49ba677994f 100644 --- a/packages/coding-agent/test/footer-width.test.ts +++ b/packages/coding-agent/test/footer-width.test.ts @@ -92,9 +92,10 @@ function createSession(options: { return session as unknown as AgentSession; } -function createFooterData(providerCount: number): ReadonlyFooterDataProvider { +function createFooterData(providerCount: number, worktree: string | null = null): ReadonlyFooterDataProvider { const provider = { getGitBranch: () => "main", + getWorktreeName: () => worktree, getExtensionStatuses: () => new Map(), getAvailableProviderCount: () => providerCount, onBranchChange: (callback: () => void) => { diff --git a/packages/coding-agent/test/git-worktree.test.ts b/packages/coding-agent/test/git-worktree.test.ts new file mode 100644 index 000000000000..4458298eb0a3 --- /dev/null +++ b/packages/coding-agent/test/git-worktree.test.ts @@ -0,0 +1,17 @@ +import { join } from "path"; +import { describe, expect, it } from "vitest"; +import { linkedWorktreeName } from "../src/core/git-worktree.ts"; + +describe("linkedWorktreeName", () => { + it("returns null for the main checkout", () => { + const repoDir = join("/repo", "app"); + const commonGitDir = join(repoDir, ".git"); + expect(linkedWorktreeName({ repoDir, commonGitDir, headPath: join(commonGitDir, "HEAD") })).toBeNull(); + }); + + it("names a linked worktree after its directory", () => { + const commonGitDir = join("/repo", "app", ".git"); + const headPath = join(commonGitDir, "worktrees", "feature-x", "HEAD"); + expect(linkedWorktreeName({ repoDir: join("/repo", "feature-x"), commonGitDir, headPath })).toBe("feature-x"); + }); +});