fix(workspaces): inspect retained indexes before Git probes

Read managed Git metadata before starting Git commands. Keep the synthetic orphan index locked so unrelated Git maintenance cannot prune the regression fixture before inspection.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-11-04 22:05:39 -06:00
1 parent 16a646ba7a
commit 9f42901683
2 files changed
+3 -2

No files matched your search

@@ -43,7 +43,7 @@ async function git(cwd: string, args: string[]) {
async function assertNoRetainedWorktree(sourcePath: string, expected: string, branch: string) { async function assertNoRetainedWorktree(sourcePath: string, expected: string, branch: string) {
const target = await physicalPath(expected); const target = await physicalPath(expected);
const common = await git(sourcePath, ["rev-parse", "--path-format=absolute", "--git-common-dir"]); const common = path.join(sourcePath, ".git");
const admin = path.join(common, "worktrees"); const admin = path.join(common, "worktrees");
const entries = await fs.readdir(admin, { withFileTypes: true }).catch((error: NodeJS.ErrnoException) => { const entries = await fs.readdir(admin, { withFileTypes: true }).catch((error: NodeJS.ErrnoException) => {
if (error.code === "ENOENT") return []; if (error.code === "ENOENT") return [];
@@ -118,6 +118,7 @@ export async function inspectArchivedGitMigrationSource(db: Pick<Db, "select">,
// not certify lost untracked files. Any surviving worktree registration // not certify lost untracked files. Any surviving worktree registration
// can retain additional index/files authority and must stay blocked. // can retain additional index/files authority and must stay blocked.
if (workspace.mode !== "isolated_workspace" || !workspace.projectId || !managedSourcePaths(workspace).includes(sourcePath)) return null; if (workspace.mode !== "isolated_workspace" || !workspace.projectId || !managedSourcePaths(workspace).includes(sourcePath)) return null;
await assertNoRetainedWorktree(sourcePath, path.join(sourcePath, ".paperclip", "worktrees", workspace.branchName), workspace.branchName);
await git(sourcePath, ["check-ref-format", `refs/heads/${workspace.branchName}`]); await git(sourcePath, ["check-ref-format", `refs/heads/${workspace.branchName}`]);
const expected = path.join(sourcePath, ".paperclip", "worktrees", workspace.branchName); const expected = path.join(sourcePath, ".paperclip", "worktrees", workspace.branchName);
if (workspace.cwd !== expected || workspace.providerRef && workspace.providerRef !== expected) return null; if (workspace.cwd !== expected || workspace.providerRef && workspace.providerRef !== expected) return null;
@@ -147,7 +148,6 @@ export async function inspectArchivedGitMigrationSource(db: Pick<Db, "select">,
ne(executionWorkspaces.id, workspace.id), ne(executionWorkspaces.id, workspace.id),
)).limit(1); )).limit(1);
if (other) throw unavailable(); if (other) throw unavailable();
await assertNoRetainedWorktree(sourcePath, expected, workspace.branchName);
const commit = await git(sourcePath, ["rev-parse", "--verify", `refs/heads/${workspace.branchName}^{commit}`]); const commit = await git(sourcePath, ["rev-parse", "--verify", `refs/heads/${workspace.branchName}^{commit}`]);
if (!/^[a-f0-9]{40}$/.test(commit)) throw unavailable(); if (!/^[a-f0-9]{40}$/.test(commit)) throw unavailable();
return { commit, sourcePath, cleanupOperationId: removalId, recipe: null, retainedBranch: workspace.branchName }; return { commit, sourcePath, cleanupOperationId: removalId, recipe: null, retainedBranch: workspace.branchName };
@@ -597,6 +597,7 @@ describe.skipIf(!support.supported)("remote workspace migration", () => {
if (mode === "orphan-index") { if (mode === "orphan-index") {
const admin = path.join(f.base, ".git", "worktrees", "orphan"); const admin = path.join(f.base, ".git", "worktrees", "orphan");
await fs.mkdir(admin, { recursive: true }); await fs.copyFile(path.join(f.base, ".git", "index"), path.join(admin, "index")); await fs.mkdir(admin, { recursive: true }); await fs.copyFile(path.join(f.base, ".git", "index"), path.join(admin, "index"));
await fs.writeFile(path.join(admin, "locked"), "retained staged recovery work\n");
} }
if (mode === "changed-branch") { if (mode === "changed-branch") {
const put = storage.putFile.bind(storage); const put = storage.putFile.bind(storage);