fix: retain snapshots for removed workspaces

This commit is contained in:
2026-08-04 08:52:11 +02:00
parent 8b046f9fb2
commit 3b23cf3714
4 changed files with 116 additions and 3 deletions
+38
View File
@@ -141,6 +141,31 @@ export class WorkspaceRegistry {
return (await this.activeState()).revisions;
}
/**
* List every intact retained snapshot, current snapshots first. Session discovery and
* retention use this rather than only the active revision so removing a workspace from
* Git cannot strand a resumable session that still pins one of its older descriptors.
*/
async listRetainedSnapshots(): Promise<WorkspaceRevision[]> {
await this.repository.ensureLayout();
return await this.lock.run(async () => {
try {
const active = await this.activeState();
const revisions = [...active.revisions];
const entries = await readdir(this.repository.snapshotsPath, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory() || entry.isSymbolicLink() || !/^[0-9a-f]{40}$/.test(entry.name)) continue;
if (entry.name === active.head) continue;
const state = await this.snapshotState(entry.name);
revisions.push(...state.revisions.filter((revision) => revision.state === "operational"));
}
return revisions;
} catch (error) {
throw workspaceError(error);
}
});
}
async read(id: string): Promise<{ workspace: WorkspaceDescriptor; revision: WorkspaceRevision }> {
const state = await this.activeState();
const revision = state.revisions.find((candidate) => candidate.id === id);
@@ -550,6 +575,19 @@ export class WorkspaceRegistry {
return JSON.parse(await readFile(path, "utf8"));
}
private async snapshotState(head: string): Promise<ActiveState> {
const manifest = await this.readSnapshotManifest(safeCommit(head));
if (this.isLegacySnapshotManifest(manifest)) {
const state = await this.deriveStateFromLegacyRevisions(manifest);
await this.migrateLegacySnapshotManifest(state, manifest);
return state;
}
const state = manifest as ActiveState;
this.assertActiveState(state);
await this.assertSnapshotIntegrity(state);
return state;
}
private async assertSnapshotIntegrity(state: ActiveState): Promise<void> {
const directory = join(this.repository.snapshotsPath, state.head);
try {