Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions packages/shared/src/git.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,29 @@ describe("isTemporaryWorktreeBranch", () => {
expect(isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/DEADBEEF`)).toBe(true);
});

it("normalizes a UUID-shaped random callback to the canonical 8-hex form", () => {
expect(buildTemporaryWorktreeBranchName(() => "f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12")).toBe(
`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e`,
);
});

it("matches legacy UUID-shaped temporary worktree refs from older mobile builds", () => {
expect(
isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-4d48-b4f2-9cf0aa54ab12`),
).toBe(true);
});

it("rejects UUID-shaped refs that are not RFC 4122 v4", () => {
// version nibble is not 4
expect(
isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-1d48-b4f2-9cf0aa54ab12`),
).toBe(false);
// variant nibble is not [89ab]
expect(
isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/f4ae4e0e-f971-4d48-c4f2-9cf0aa54ab12`),
).toBe(false);
});

it("rejects non-temporary refName names", () => {
expect(isTemporaryWorktreeBranch(`${WORKTREE_BRANCH_PREFIX}/feature/demo`)).toBe(false);
expect(isTemporaryWorktreeBranch("main")).toBe(false);
Expand Down
15 changes: 13 additions & 2 deletions packages/shared/src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import * as Result from "effect/Result";
import { detectSourceControlProviderFromRemoteUrl } from "./sourceControl.ts";

export const WORKTREE_BRANCH_PREFIX = "t3code";
const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp(`^${WORKTREE_BRANCH_PREFIX}\\/[0-9a-f]{8}$`);
// Canonical form is `t3code/<8 hex>`. Older mobile builds generated `t3code/<uuid>`
// via Crypto.randomUUID() (always RFC 4122 v4), so the matcher also accepts exactly
// that shape — version nibble `4`, variant nibble `[89ab]` — to keep those threads
// eligible for branch regeneration without loosening beyond what was ever generated.
const TEMP_WORKTREE_BRANCH_PATTERN = new RegExp(
`^${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})$`,
);
Comment thread
cursor[bot] marked this conversation as resolved.

/**
* Sanitize an arbitrary string into a valid, lowercase git refName fragment.
Expand Down Expand Up @@ -89,7 +95,12 @@ export function deriveLocalBranchNameFromRemoteRef(branchName: string): string {
export function buildTemporaryWorktreeBranchName(
randomHex: (byteLength: number) => string,
): string {
const token = randomHex(4).toLowerCase();
// Normalize to exactly 8 lowercase hex chars so a UUID-shaped callback
// still produces the canonical temporary branch form.
const token = randomHex(4)
.toLowerCase()
.replace(/[^0-9a-f]/g, "")
.slice(0, 8);
return `${WORKTREE_BRANCH_PREFIX}/${token}`;
}

Expand Down
Loading