| author | |
| committer | |
| log | 571a8b44bdd4f0f0b0464ef24240021689616e7d |
| tree | 667e9f86faca3e6f42d78bdba864ba33916d4920 |
| parent | b6e1b39335ffd34583c94f5b1e5e2c80520729fe |
| signature | Signed by PGP key B5690EEEBB952194 |
2 files changed, 36 insertions(+), 2 deletions(-)
packages/shared/src/git.test.ts+23| ... | ... | @@ -71,6 +71,29 @@ describe("isTemporaryWorktreeBranch", () => { |
| 71 | 71 | expect(isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/DEADBEEF`)).toBe(true); |
| 72 | 72 | }); |
| 73 | 73 | |
| 74 | it("normalizes a UUID-shaped random callback to the canonical 8-hex form", () => { | |
| 75 | expect(buildTemporaryWorktreeBranchName(() => "f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12")).toBe( | |
| 76 | `${WORKTREE_BRANCH_PREFIX}/f4ae4e0e`, | |
| 77 | ); | |
| 78 | }); | |
| 79 | ||
| 80 | it("matches legacy UUID-shaped temporary worktree refs from older mobile builds", () => { | |
| 81 | expect( | |
| 82 | isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12`), | |
| 83 | ).toBe(true); | |
| 84 | }); | |
| 85 | ||
| 86 | it("rejects UUID-shaped refs that are not RFC 4122 v4", () => { | |
| 87 | // version nibble is not 4 | |
| 88 | expect( | |
| 89 | isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-1d48-b4f2-9cf0aa54ab12`), | |
| 90 | ).toBe(false); | |
| 91 | // variant nibble is not [89ab] | |
| 92 | expect( | |
| 93 | isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-4d48-c4f2-9cf0aa54ab12`), | |
| 94 | ).toBe(false); | |
| 95 | }); | |
| 96 | ||
| 74 | 97 | it("rejects non-temporary refName names", () => { |
| 75 | 98 | expect(isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/feature/demo`)).toBe(false); |
| 76 | 99 | expect(isTemporaryWorktreeBranch("main")).toBe(false); |
packages/shared/src/git.ts+13-2| ... | ... | @@ -11,7 +11,13 @@ import * as Result from "effect/Result"; |
| 11 | 11 | import { detectSourceControlProviderFromRemoteUrl } from "./sourceControl.ts"; |
| 12 | 12 | |
| 13 | 13 | export const WORKTREE_BRANCH_PREFIX = "t3code"; |
| 14 | const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp(`^${WORKTREE_BRANCH_PREFIX}\\/[0-9a-f]{8}$`); | |
| 14 | // Canonical form is `t3code/<8 hex>`. Older mobile builds generated `t3code/<uuid>` | |
| 15 | // via Crypto.randomUUID() (always RFC 4122 v4), so the matcher also accepts exactly | |
| 16 | // that shape — version nibble `4`, variant nibble `[89ab]` — to keep those threads | |
| 17 | // eligible for branch regeneration without loosening beyond what was ever generated. | |
| 18 | const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp( | |
| 19 | `^${WORKTREE_BRANCH_PREFIX}\\/(?:[0-9a-f]{8}|[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$`, | |
| 20 | ); | |
| 15 | 21 | |
| 16 | 22 | /** |
| 17 | 23 | * Sanitize an arbitrary string into a valid, lowercase git refName fragment. |
| ... | ... | @@ -89,7 +95,12 @@ export function deriveLocalBranchNameFromRemoteRef(branchName: string): string { |
| 89 | 95 | export function buildTemporaryWorktreeBranchName( |
| 90 | 96 | randomHex: (byteLength: number) => string, |
| 91 | 97 | ): string { |
| 92 | const token = randomHex(4).toLowerCase(); | |
| 98 | // Normalize to exactly 8 lowercase hex chars so a UUID-shaped callback | |
| 99 | // still produces the canonical temporary branch form. | |
| 100 | const token = randomHex(4) | |
| 101 | .toLowerCase() | |
| 102 | .replace(/[^0-9a-f]/g, "") | |
| 103 | .slice(0, 8); | |
| 93 | 104 | return `${WORKTREE_BRANCH_PREFIX}/${token}`; |
| 94 | 105 | } |
| 95 | 106 |