- move branch-sync decision into `BranchToolbar.logic.ts` - avoid auto-sync when env mode is `worktree` or thread has worktree path - add unit tests for sync and no-sync cases
Unified · Split
| @@ -0,0 +1,56 @@ | ||
| 1 | +import type { GitBranch } from "@t3tools/contracts"; | |
| 2 | +import { describe, expect, it } from "vitest"; | |
| 3 | +import { deriveSyncedLocalBranch } from "./BranchToolbar.logic"; | |
| 4 | + | |
| 5 | +const branches: GitBranch[] = [ | |
| 6 | + { | |
| 7 | + name: "main", | |
| 8 | + current: true, | |
| 9 | + isDefault: true, | |
| 10 | + worktreePath: null, | |
| 11 | + }, | |
| 12 | + { | |
| 13 | + name: "feature/demo", | |
| 14 | + current: false, | |
| 15 | + isDefault: false, | |
| 16 | + worktreePath: null, | |
| 17 | + }, | |
| 18 | +]; | |
| 19 | + | |
| 20 | +describe("deriveSyncedLocalBranch", () => { | |
| 21 | + it("syncs to git current branch in local mode", () => { | |
| 22 | + const result = deriveSyncedLocalBranch({ | |
| 23 | + activeThreadId: "thread-1", | |
| 24 | + activeWorktreePath: null, | |
| 25 | + envMode: "local", | |
| 26 | + activeThreadBranch: "feature/demo", | |
| 27 | + queryBranches: branches, | |
| 28 | + }); | |
| 29 | + | |
| 30 | + expect(result).toBe("main"); | |
| 31 | + }); | |
| 32 | + | |
| 33 | + it("does not sync when creating a new worktree", () => { | |
| 34 | + const result = deriveSyncedLocalBranch({ | |
| 35 | + activeThreadId: "thread-1", | |
| 36 | + activeWorktreePath: null, | |
| 37 | + envMode: "worktree", | |
| 38 | + activeThreadBranch: "feature/demo", | |
| 39 | + queryBranches: branches, | |
| 40 | + }); | |
| 41 | + | |
| 42 | + expect(result).toBeNull(); | |
| 43 | + }); | |
| 44 | + | |
| 45 | + it("does not sync when thread already targets a worktree path", () => { | |
| 46 | + const result = deriveSyncedLocalBranch({ | |
| 47 | + activeThreadId: "thread-1", | |
| 48 | + activeWorktreePath: "/tmp/repo/worktrees/feature-demo", | |
| 49 | + envMode: "local", | |
| 50 | + activeThreadBranch: "feature/demo", | |
| 51 | + queryBranches: branches, | |
| 52 | + }); | |
| 53 | + | |
| 54 | + expect(result).toBeNull(); | |
| 55 | + }); | |
| 56 | +}); | |
| @@ -0,0 +1,28 @@ | ||
| 1 | +import type { GitBranch } from "@t3tools/contracts"; | |
| 2 | + | |
| 3 | +interface DeriveSyncedLocalBranchInput { | |
| 4 | + activeThreadId: string | undefined; | |
| 5 | + activeWorktreePath: string | null; | |
| 6 | + envMode: "local" | "worktree"; | |
| 7 | + activeThreadBranch: string | null; | |
| 8 | + queryBranches: GitBranch[] | undefined; | |
| 9 | +} | |
| 10 | + | |
| 11 | +export function deriveSyncedLocalBranch({ | |
| 12 | + activeThreadId, | |
| 13 | + activeWorktreePath, | |
| 14 | + envMode, | |
| 15 | + activeThreadBranch, | |
| 16 | + queryBranches, | |
| 17 | +}: DeriveSyncedLocalBranchInput): string | null { | |
| 18 | + if (!activeThreadId || activeWorktreePath || envMode !== "local") { | |
| 19 | + return null; | |
| 20 | + } | |
| 21 | + | |
| 22 | + const currentBranch = queryBranches?.find((branch) => branch.current); | |
| 23 | + if (!currentBranch || currentBranch.name === activeThreadBranch) { | |
| 24 | + return null; | |
| 25 | + } | |
| 26 | + | |
| 27 | + return currentBranch.name; | |
| 28 | +} | |
| @@ -9,6 +9,7 @@ import { · 8 unmodified lines | ||
| 9 | 9 | gitCreateBranchAndCheckoutMutationOptions, |
| 10 | 10 | } from "../lib/gitReactQuery"; |
| 11 | 11 | import { useStore } from "../store"; |
| 12 | +import { deriveSyncedLocalBranch } from "./BranchToolbar.logic"; | |
| 12 | 13 | import { Button } from "./ui/button"; |
| 13 | 14 | import { |
| 14 | 15 | Combobox, |
| @@ -86,17 +87,22 @@ export default function BranchToolbar({ · 71 unmodified lines | ||
| 86 | 87 | // Keep thread branch synced to git current branch for local threads. |
| 87 | 88 | const queryBranches = branchesQuery.data?.branches; |
| 88 | 89 | useEffect(() => { |
| 89 | − if (!activeThreadId || activeWorktreePath) return; | |
| 90 | − const current = queryBranches?.find((branch) => branch.current); | |
| 91 | − if (!current) return; | |
| 92 | − if (current.name === activeThreadBranch) return; | |
| 90 | + const syncedBranch = deriveSyncedLocalBranch({ | |
| 91 | + activeThreadId, | |
| 92 | + activeWorktreePath, | |
| 93 | + envMode, | |
| 94 | + activeThreadBranch, | |
| 95 | + queryBranches, | |
| 96 | + }); | |
| 97 | + if (!syncedBranch) return; | |
| 98 | + | |
| 93 | 99 | dispatch({ |
| 94 | 100 | type: "SET_THREAD_BRANCH", |
| 95 | 101 | threadId: activeThreadId, |
| 96 | − branch: current.name, | |
| 102 | + branch: syncedBranch, | |
| 97 | 103 | worktreePath: null, |
| 98 | 104 | }); |
| 99 | − }, [activeThreadId, activeWorktreePath, activeThreadBranch, queryBranches, dispatch]); | |
| 105 | + }, [activeThreadId, activeWorktreePath, activeThreadBranch, queryBranches, envMode, dispatch]); | |
| 100 | 106 | |
| 101 | 107 | useEffect(() => { |
| 102 | 108 | if (isBranchMenuOpen) return; |