diff --git a/apps/web/src/components/BranchToolbar.logic.test.ts b/apps/web/src/components/BranchToolbar.logic.test.ts index 08e6f7633dee3..903a20066b39e 100644 --- a/apps/web/src/components/BranchToolbar.logic.test.ts +++ b/apps/web/src/components/BranchToolbar.logic.test.ts @@ -160,6 +160,18 @@ describe("dedupeRemoteBranchesWithLocalMatches", () => { describe("resolveBranchSelectionTarget", () => { it("reuses an existing secondary worktree for the selected ref", () => { + expect( + resolveBranchSelectionTarget({ + activeProjectCwd: "/repo/nested-checkout/packages/app", + activeProjectCheckoutRoot: "/repo/nested-checkout", + activeWorktreePath: null, + refName: { isDefault: true, worktreePath: "/repo" }, + }), + ).toEqual({ + checkoutCwd: "/repo", + nextWorktreePath: "/repo", + reuseExistingWorktree: true, + }); expect( resolveBranchSelectionTarget({ activeProjectCwd: "/repo", @@ -177,20 +189,31 @@ describe("resolveBranchSelectionTarget", () => { }); it("switches back to the main repo when the ref already lives there", () => { - expect( - resolveBranchSelectionTarget({ - activeProjectCwd: "/repo", - activeWorktreePath: "/repo/.t3/worktrees/feature-a", - refName: { - isDefault: true, - worktreePath: "/repo", - }, - }), - ).toEqual({ - checkoutCwd: "/repo", - nextWorktreePath: null, - reuseExistingWorktree: true, - }); + // A subdirectory project's own checkout is reported at the repository root. + // The checkout root is undefined until the project's status loads. + for (const [activeProjectCwd, activeWorktreePath, activeProjectCheckoutRoot] of [ + ["/repo", "/repo/.t3/worktrees/feature-a", "/repo"], + ["/repo/packages/app", "/repo/.t3/worktrees/feature-a", "/repo"], + ["/repo/packages/app", null, "/repo"], + ["/repo/packages/app", "/repo/.t3/worktrees/feature-a", undefined], + ["/repo/packages/app", null, undefined], + ] as const) { + expect( + resolveBranchSelectionTarget({ + activeProjectCwd, + activeProjectCheckoutRoot, + activeWorktreePath, + refName: { + isDefault: true, + worktreePath: "/repo", + }, + }), + ).toEqual({ + checkoutCwd: "/repo", + nextWorktreePath: null, + reuseExistingWorktree: true, + }); + } }); it("keeps checkout in the current worktree for non-default refs", () => { diff --git a/apps/web/src/components/BranchToolbar.logic.ts b/apps/web/src/components/BranchToolbar.logic.ts index e22dcfaddedeb..a967c3171fbd1 100644 --- a/apps/web/src/components/BranchToolbar.logic.ts +++ b/apps/web/src/components/BranchToolbar.logic.ts @@ -7,6 +7,7 @@ import type { } from "@t3tools/contracts"; import * as Schema from "effect/Schema"; import { sanitizeNewRefName } from "@t3tools/shared/git"; +import { workspaceRelativeFilePath } from "@t3tools/shared/path"; import { toSortableTimestamp } from "@t3tools/client-runtime/state/thread-sort"; export { deriveLocalBranchNameFromRemoteRef, sanitizeNewRefName } from "@t3tools/shared/git"; @@ -247,6 +248,7 @@ export function resolveLocalCheckoutBranchMismatch(input: { export function resolveBranchSelectionTarget(input: { activeProjectCwd: string; + activeProjectCheckoutRoot?: string | undefined; activeWorktreePath: string | null; refName: Pick; }): { @@ -257,9 +259,14 @@ export function resolveBranchSelectionTarget(input: { const { activeProjectCwd, activeWorktreePath, refName } = input; if (refName.worktreePath) { + // Until the project's checkout root loads, a checkout containing the project counts as its own. + const isProjectCheckout = + input.activeProjectCheckoutRoot === undefined + ? workspaceRelativeFilePath(activeProjectCwd, refName.worktreePath) !== null + : workspaceRelativeFilePath(input.activeProjectCheckoutRoot, refName.worktreePath) === "."; return { checkoutCwd: refName.worktreePath, - nextWorktreePath: refName.worktreePath === activeProjectCwd ? null : refName.worktreePath, + nextWorktreePath: isProjectCheckout ? null : refName.worktreePath, reuseExistingWorktree: true, }; } diff --git a/apps/web/src/components/BranchToolbarBranchSelector.tsx b/apps/web/src/components/BranchToolbarBranchSelector.tsx index fab250f6adaf8..9782f258afe19 100644 --- a/apps/web/src/components/BranchToolbarBranchSelector.tsx +++ b/apps/web/src/components/BranchToolbarBranchSelector.tsx @@ -224,6 +224,12 @@ export function BranchToolbarBranchSelector({ input: { cwd: branchCwd }, }), ); + const projectStatusQuery = useEnvironmentQuery( + activeProjectCwd === null + ? null + : vcsEnvironment.status({ environmentId, input: { cwd: activeProjectCwd } }), + ); + const activeProjectCheckoutRoot = projectStatusQuery.data?.workingTree.rootPath; const trimmedBranchQuery = branchQuery.trim(); const branchRefState = usePaginatedBranches({ environmentId, @@ -389,6 +395,7 @@ export function BranchToolbarBranchSelector({ const selectionTarget = resolveBranchSelectionTarget({ activeProjectCwd, + activeProjectCheckoutRoot, activeWorktreePath, refName, }); @@ -631,6 +638,7 @@ export function BranchToolbarBranchSelector({ (!activeProjectCwd || !resolveBranchSelectionTarget({ activeProjectCwd, + activeProjectCheckoutRoot, activeWorktreePath, refName, }).reuseExistingWorktree))