fix: recover worktree bootstrap from stale index.lock

Retries transient index.lock conflicts during worktree population
Removes unchanged stale locks automatically and continues bootstrap
Adds coverage for stale lock recovery
This commit is contained in:
Bohdan Triapitsyn
2026-07-18 22:06:25 +03:00
parent fee6a55bab
commit e9d93a6744
4 changed files with 162 additions and 2 deletions
@@ -10,6 +10,7 @@ import {
cherryPick,
createWorktree,
getStatus,
populateWorktreeWithLockRecovery,
removeWorktree,
resolvePrimaryWorktreeRoot,
resolveWorktreeTopLevel,
@@ -321,6 +322,28 @@ describe('worktree root resolution', () => {
// ---------------------------------------------------------------------------
describe('createWorktree', () => {
it('recovers from an unchanged stale index lock while populating a worktree', async () => {
if (!canRunGit()) return;
const repo = createTempDir();
const worktree = createTempDir();
runGit(repo, ['init', '-b', 'main']);
runGit(repo, ['config', 'user.email', 'test@example.com']);
runGit(repo, ['config', 'user.name', 'Test User']);
fs.writeFileSync(path.join(repo, 'README.md'), '# Test\n');
runGit(repo, ['add', 'README.md']);
runGit(repo, ['commit', '-m', 'Initial commit']);
fs.rmSync(worktree, { recursive: true, force: true });
runGit(repo, ['worktree', 'add', '--no-checkout', '-b', 'feature/stale-lock', worktree, 'HEAD']);
const lockPath = runGit(worktree, ['rev-parse', '--git-path', 'index.lock']).trim();
fs.writeFileSync(lockPath, 'stale');
await expect(populateWorktreeWithLockRecovery(worktree)).resolves.toBeUndefined();
expect(fs.existsSync(lockPath)).toBe(false);
expect(fs.readFileSync(path.join(worktree, 'README.md'), 'utf8')).toBe('# Test\n');
});
it('preflights fast create branch-in-use failures before creating the candidate directory', async () => {
if (!canRunGit()) return;