| author | |
| committer | |
| log | a2ffb122e2041731d1efe24d999aefe4e1ff7c03 |
| tree | 33117a5c8289eadb8563b431e67f226f0fc2fca2 |
| parent | 3957a958184650656dbbab6d73d9882de9855bd5 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>3 files changed, 150 insertions(+), 16 deletions(-)
apps/web/src/components/BranchToolbar.logic.test.ts+96| ... | ... | @@ -9,6 +9,7 @@ import { |
| 9 | 9 | resolveDraftEnvModeAfterBranchChange, |
| 10 | 10 | resolveEffectiveEnvMode, |
| 11 | 11 | resolveEnvModeLabel, |
| 12 | resolveBranchTriggerLabel, | |
| 12 | 13 | resolveBranchToolbarPrBranch, |
| 13 | 14 | resolveBranchToolbarValue, |
| 14 | 15 | resolveLockedWorkspaceLabel, |
| ... | ... | @@ -174,6 +175,101 @@ describe("resolveBranchToolbarValue", () => { |
| 174 | 175 | }); |
| 175 | 176 | }); |
| 176 | 177 | |
| 178 | describe("resolveBranchTriggerLabel", () => { | |
| 179 | it("shows the origin ref when a new worktree will start from origin", () => { | |
| 180 | expect( | |
| 181 | resolveBranchTriggerLabel({ | |
| 182 | activeWorktreePath: null, | |
| 183 | effectiveEnvMode: "worktree", | |
| 184 | resolvedActiveBranch: "main", | |
| 185 | resolvedActiveBranchIsRemote: false, | |
| 186 | startFromOrigin: true, | |
| 187 | }), | |
| 188 | ).toBe("From origin/main"); | |
| 189 | }); | |
| 190 | ||
| 191 | it("shows the origin ref for local branch names that contain slashes", () => { | |
| 192 | expect( | |
| 193 | resolveBranchTriggerLabel({ | |
| 194 | activeWorktreePath: null, | |
| 195 | effectiveEnvMode: "worktree", | |
| 196 | resolvedActiveBranch: "feature/demo", | |
| 197 | resolvedActiveBranchIsRemote: false, | |
| 198 | startFromOrigin: true, | |
| 199 | }), | |
| 200 | ).toBe("From origin/feature/demo"); | |
| 201 | }); | |
| 202 | ||
| 203 | it("shows the local ref when start from origin is disabled", () => { | |
| 204 | expect( | |
| 205 | resolveBranchTriggerLabel({ | |
| 206 | activeWorktreePath: null, | |
| 207 | effectiveEnvMode: "worktree", | |
| 208 | resolvedActiveBranch: "main", | |
| 209 | resolvedActiveBranchIsRemote: false, | |
| 210 | startFromOrigin: false, | |
| 211 | }), | |
| 212 | ).toBe("From main"); | |
| 213 | }); | |
| 214 | ||
| 215 | it("does not duplicate the origin prefix for an explicit remote ref", () => { | |
| 216 | expect( | |
| 217 | resolveBranchTriggerLabel({ | |
| 218 | activeWorktreePath: null, | |
| 219 | effectiveEnvMode: "worktree", | |
| 220 | resolvedActiveBranch: "origin/feature/demo", | |
| 221 | resolvedActiveBranchIsRemote: true, | |
| 222 | startFromOrigin: true, | |
| 223 | }), | |
| 224 | ).toBe("From origin/feature/demo"); | |
| 225 | }); | |
| 226 | ||
| 227 | it("preserves an explicit ref from a non-origin remote", () => { | |
| 228 | expect( | |
| 229 | resolveBranchTriggerLabel({ | |
| 230 | activeWorktreePath: null, | |
| 231 | effectiveEnvMode: "worktree", | |
| 232 | resolvedActiveBranch: "upstream/feature/demo", | |
| 233 | resolvedActiveBranchIsRemote: true, | |
| 234 | startFromOrigin: true, | |
| 235 | }), | |
| 236 | ).toBe("From upstream/feature/demo"); | |
| 237 | }); | |
| 238 | ||
| 239 | it("keeps current-checkout labels and empty state unchanged", () => { | |
| 240 | expect( | |
| 241 | resolveBranchTriggerLabel({ | |
| 242 | activeWorktreePath: null, | |
| 243 | effectiveEnvMode: "local", | |
| 244 | resolvedActiveBranch: "main", | |
| 245 | resolvedActiveBranchIsRemote: false, | |
| 246 | startFromOrigin: true, | |
| 247 | }), | |
| 248 | ).toBe("main"); | |
| 249 | expect( | |
| 250 | resolveBranchTriggerLabel({ | |
| 251 | activeWorktreePath: null, | |
| 252 | effectiveEnvMode: "worktree", | |
| 253 | resolvedActiveBranch: null, | |
| 254 | resolvedActiveBranchIsRemote: null, | |
| 255 | startFromOrigin: true, | |
| 256 | }), | |
| 257 | ).toBe("Select ref"); | |
| 258 | }); | |
| 259 | ||
| 260 | it("does not fabricate an origin ref while branch metadata is loading", () => { | |
| 261 | expect( | |
| 262 | resolveBranchTriggerLabel({ | |
| 263 | activeWorktreePath: null, | |
| 264 | effectiveEnvMode: "worktree", | |
| 265 | resolvedActiveBranch: "upstream/feature/demo", | |
| 266 | resolvedActiveBranchIsRemote: null, | |
| 267 | startFromOrigin: true, | |
| 268 | }), | |
| 269 | ).toBe("From upstream/feature/demo"); | |
| 270 | }); | |
| 271 | }); | |
| 272 | ||
| 177 | 273 | describe("resolveBranchToolbarPrBranch", () => { |
| 178 | 274 | it("uses the explicit thread branch when it matches the displayed branch", () => { |
| 179 | 275 | expect( |
apps/web/src/components/BranchToolbar.logic.ts+27| ... | ... | @@ -156,6 +156,33 @@ export function resolveBranchToolbarValue(input: { |
| 156 | 156 | return currentGitBranch ?? activeThreadBranch; |
| 157 | 157 | } |
| 158 | 158 | |
| 159 | export function resolveBranchTriggerLabel(input: { | |
| 160 | activeWorktreePath: string | null; | |
| 161 | effectiveEnvMode: EnvMode; | |
| 162 | resolvedActiveBranch: string | null; | |
| 163 | resolvedActiveBranchIsRemote: boolean | null; | |
| 164 | startFromOrigin: boolean; | |
| 165 | }): string { | |
| 166 | const { | |
| 167 | activeWorktreePath, | |
| 168 | effectiveEnvMode, | |
| 169 | resolvedActiveBranch, | |
| 170 | resolvedActiveBranchIsRemote, | |
| 171 | startFromOrigin, | |
| 172 | } = input; | |
| 173 | if (!resolvedActiveBranch) { | |
| 174 | return "Select ref"; | |
| 175 | } | |
| 176 | if (effectiveEnvMode === "worktree" && !activeWorktreePath) { | |
| 177 | const baseRef = | |
| 178 | startFromOrigin && resolvedActiveBranchIsRemote === false | |
| 179 | ? `origin/${resolvedActiveBranch}` | |
| 180 | : resolvedActiveBranch; | |
| 181 | return `From ${baseRef}`; | |
| 182 | } | |
| 183 | return resolvedActiveBranch; | |
| 184 | } | |
| 185 | ||
| 159 | 186 | export function resolveBranchToolbarPrBranch(input: { |
| 160 | 187 | activeThreadBranch: string | null; |
| 161 | 188 | resolvedActiveBranch: string | null; |
apps/web/src/components/BranchToolbarBranchSelector.tsx+27-16| ... | ... | @@ -35,6 +35,7 @@ import { parsePullRequestReference } from "../pullRequestReference"; |
| 35 | 35 | import { getSourceControlPresentation } from "../sourceControlPresentation"; |
| 36 | 36 | import { |
| 37 | 37 | deriveLocalBranchNameFromRemoteRef, |
| 38 | resolveBranchTriggerLabel, | |
| 38 | 39 | resolveBranchToolbarPrBranch, |
| 39 | 40 | resolveBranchSelectionTarget, |
| 40 | 41 | resolveBranchToolbarValue, |
| ... | ... | @@ -81,21 +82,6 @@ function toBranchActionErrorMessage(error: unknown): string { |
| 81 | 82 | return error instanceof Error ? error.message : "An error occurred."; |
| 82 | 83 | } |
| 83 | 84 | |
| 84 | function getBranchTriggerLabel(input: { | |
| 85 | activeWorktreePath: string | null; | |
| 86 | effectiveEnvMode: "local" | "worktree"; | |
| 87 | resolvedActiveBranch: string | null; | |
| 88 | }): string { | |
| 89 | const { activeWorktreePath, effectiveEnvMode, resolvedActiveBranch } = input; | |
| 90 | if (!resolvedActiveBranch) { | |
| 91 | return "Select ref"; | |
| 92 | } | |
| 93 | if (effectiveEnvMode === "worktree" && !activeWorktreePath) { | |
| 94 | return `From ${resolvedActiveBranch}`; | |
| 95 | } | |
| 96 | return resolvedActiveBranch; | |
| 97 | } | |
| 98 | ||
| 99 | 85 | export function BranchToolbarBranchSelector({ |
| 100 | 86 | className, |
| 101 | 87 | environmentId, |
| ... | ... | @@ -308,6 +294,29 @@ export function BranchToolbarBranchSelector({ |
| 308 | 294 | canonicalActiveBranch, |
| 309 | 295 | (_currentBranch: string | null, optimisticBranch: string | null) => optimisticBranch, |
| 310 | 296 | ); |
| 297 | const listedActiveBranch = | |
| 298 | resolvedActiveBranch === null ? null : (branchByName.get(resolvedActiveBranch) ?? null); | |
| 299 | const activeBranchRefQuery = useEnvironmentQuery( | |
| 300 | branchCwd !== null && resolvedActiveBranch !== null | |
| 301 | ? vcsEnvironment.listRefs({ | |
| 302 | environmentId, | |
| 303 | input: { | |
| 304 | cwd: branchCwd, | |
| 305 | query: resolvedActiveBranch, | |
| 306 | limit: 10, | |
| 307 | }, | |
| 308 | }) | |
| 309 | : null, | |
| 310 | ); | |
| 311 | const queriedActiveBranch = activeBranchRefQuery.data?.refs.find( | |
| 312 | (refName) => refName.name === resolvedActiveBranch, | |
| 313 | ); | |
| 314 | const resolvedActiveBranchIsRemote = | |
| 315 | listedActiveBranch !== null | |
| 316 | ? listedActiveBranch.isRemote === true | |
| 317 | : queriedActiveBranch | |
| 318 | ? queriedActiveBranch.isRemote === true | |
| 319 | : null; | |
| 311 | 320 | const [isBranchActionPending, startBranchActionTransition] = useTransition(); |
| 312 | 321 | const totalBranchCount = branchRefState.data?.totalCount ?? 0; |
| 313 | 322 | const branchStatusText = isInitialBranchesLoadPending |
| ... | ... | @@ -584,10 +593,12 @@ export function BranchToolbarBranchSelector({ |
| 584 | 593 | maybeFetchNextBranchPage(); |
| 585 | 594 | }, [refs.length, maybeFetchNextBranchPage]); |
| 586 | 595 | |
| 587 | const triggerLabel = getBranchTriggerLabel({ | |
| 596 | const triggerLabel = resolveBranchTriggerLabel({ | |
| 588 | 597 | activeWorktreePath, |
| 589 | 598 | effectiveEnvMode, |
| 590 | 599 | resolvedActiveBranch, |
| 600 | resolvedActiveBranchIsRemote, | |
| 601 | startFromOrigin, | |
| 591 | 602 | }); |
| 592 | 603 | |
| 593 | 604 | // PR pill shown next to the branch selector when the active branch has one. |