mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 01:47:24 +00:00
fix(worktree): detect case and Unicode aliases of checked-out branches (#18462)
On case-insensitive or normalization-insensitive filesystems (macOS), Git resolves `feature/TEST` or an NFD-composed branch name to the stored ref of an already checked-out branch. The busy-branch check only compared exact names, so creating a worktree for such an alias passed the check and then failed in git. Fold case/Unicode for the comparison and only treat the alias as busy when the filesystem does not store both spellings as distinct refs. Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
@@ -70,3 +70,35 @@ describe('explicit local branch reuse on the execution host', () => {
|
||||
expect(worktrees).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('filesystem aliases of checked-out branches', () => {
|
||||
const nfcBranch = 'feature/수신'.normalize('NFC')
|
||||
const nfdBranch = nfcBranch.normalize('NFD')
|
||||
|
||||
function mockGit(checkedOutBranch: string, listRef: (ref: string) => string): void {
|
||||
git.mockImplementation(async (args: string[]) =>
|
||||
args[0] === 'for-each-ref' ? { stdout: listRef(args[2]) } : { stdout: 'abc123\n' }
|
||||
)
|
||||
worktrees.mockResolvedValue([{ path: '/busy', branch: `refs/heads/${checkedOutBranch}` }])
|
||||
}
|
||||
|
||||
it('rejects an NFD name that macOS Git resolves to a checked-out NFC branch (#18447)', async () => {
|
||||
mockGit(nfcBranch, () => `refs/heads/${nfcBranch}\n`)
|
||||
|
||||
await expect(canCheckoutExistingLocalBranch('/repo', nfdBranch, nfdBranch)).resolves.toBe(false)
|
||||
})
|
||||
|
||||
it('rejects a case alias of a checked-out branch on a case-insensitive filesystem', async () => {
|
||||
mockGit('feature/test', () => '')
|
||||
|
||||
await expect(
|
||||
canCheckoutExistingLocalBranch('/repo', 'feature/TEST', 'feature/TEST')
|
||||
).resolves.toBe(false)
|
||||
})
|
||||
|
||||
it('allows a distinct alias on filesystems that store both spellings', async () => {
|
||||
mockGit(nfcBranch, (ref) => `${ref}\n`)
|
||||
|
||||
await expect(canCheckoutExistingLocalBranch('/repo', nfdBranch, nfdBranch)).resolves.toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -96,12 +96,15 @@ export async function canCheckoutExistingLocalBranch(
|
||||
}
|
||||
const worktrees = await listWorktrees(repoPath, gitOptions)
|
||||
let busy = worktrees.find((worktree) => normalizeLocalBranchName(worktree.branch) === branchName)
|
||||
if (explicitReuse && !busy) {
|
||||
if (!busy) {
|
||||
// Why: macOS Git resolves case and NFC/NFD aliases to one stored ref; Linux keeps them distinct.
|
||||
// Explicit reuse already verified that the requested spelling is stored exactly.
|
||||
for (const worktree of worktrees) {
|
||||
const checkedOutBranch = normalizeLocalBranchName(worktree.branch)
|
||||
if (
|
||||
foldGitRefNameCase(checkedOutBranch) === foldGitRefNameCase(branchName) &&
|
||||
!(await hasExactBranchRef(checkedOutBranch))
|
||||
((!explicitReuse && !(await hasExactBranchRef(branchName))) ||
|
||||
!(await hasExactBranchRef(checkedOutBranch)))
|
||||
) {
|
||||
busy = worktree
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user