mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Remove a worktree when its last workspace is archived (#1562)
* Remove a worktree when its last workspace is archived
Archiving is now workspace-centric: the UI always archives a workspace,
and a worktree's directory is removed only when its last referencing
workspace is archived (derived from ownership + a single reference
count). The "remove the worktree from disk?" prompt is gone; archiving
warns only when the workspace is dirty or has unpushed commits.
MCP archive_worktree and the CLI keep the explicit lower-level behavior:
archive every workspace backing the directory, then remove it.
Auto-archive-after-merge and create-agent auto-archive respect the
reference count like any other workspace archival.
The policy now lives in two modules - a server archive service and a
client archive hook - with the reference-count check in exactly one
place. The deleteWorktreeFromDisk request field is retained but ignored
for wire back-compat.
* Tidy archive-policy internals and close test gaps
Follow-up polish on the archive-policy consolidation; no behavior change.
Structure: the server archive service is renamed to reflect that it now
owns all archive policy (the file and its types no longer say "worktree"
where they mean "archive"); the client archive input derives the
workspace kind from the canonical type instead of re-listing it; the
not-found target is typed string|null instead of an empty-string
sentinel; a redundant workspace-update emit and the ambiguous
worktrees-root naming are cleaned up. Wire, SDK, and keybinding names are
deliberately left unchanged for back-compat.
Tests: re-home the archiving-state lifecycle and per-workspace snapshot
assertions; pin worktree-kind acceptance through archive_workspace, the
ignored deleteWorktreeFromDisk field, and three-workspace archive-all;
add a Playwright spec for the dirty/unpushed worktree archive flow (the
warning gates it, confirming removes the directory) and the bulk
remove-project confirm branch.
* Fix the hanging worktree-archive risk-warning e2e spec
The spec added in the previous commit hung deterministically, locally and
in CI. Two test-only bugs, no product change.
The archive click opens a synchronous window.confirm(); the spec
registered a passive dialog waiter but only dismissed the dialog after
awaiting the click, so the click never resolved (Playwright won't settle
a click while a dialog is open) - a circular wait. Answer the dialog
inline via page.once("dialog", ...) before the click resolves, matching
the proven worktree-archive spec.
The seed also committed on a never-pushed branch, so aheadOfOrigin was
null and the "1 unpushed commit" warning never appeared. Push the branch
to set its upstream before the local commit, so it reports exactly one
unpushed commit.
Verified: the spec now passes in ~6s (was a 45s timeout); both the
dismiss-gates and accept-removes paths run.
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { FileExplorerRequestSchema, SessionOutboundMessageSchema } from "./messages.js";
|
||||
import {
|
||||
FileExplorerRequestSchema,
|
||||
PaseoWorktreeArchiveRequestSchema,
|
||||
SessionOutboundMessageSchema,
|
||||
} from "./messages.js";
|
||||
|
||||
function workspaceDescriptor(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
@@ -161,3 +165,35 @@ describe("file explorer request compatibility", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("paseo worktree archive request compatibility", () => {
|
||||
test("omitted scope defaults to workspace", () => {
|
||||
const parsed = PaseoWorktreeArchiveRequestSchema.parse({
|
||||
type: "paseo_worktree_archive_request",
|
||||
worktreePath: "/repo/app",
|
||||
requestId: "req-old-scope",
|
||||
});
|
||||
expect(parsed.scope).toBe("workspace");
|
||||
});
|
||||
|
||||
test("scope worktree parses", () => {
|
||||
const parsed = PaseoWorktreeArchiveRequestSchema.parse({
|
||||
type: "paseo_worktree_archive_request",
|
||||
worktreePath: "/repo/app",
|
||||
scope: "worktree",
|
||||
requestId: "req-worktree-scope",
|
||||
});
|
||||
expect(parsed.scope).toBe("worktree");
|
||||
});
|
||||
|
||||
test("unknown extra field is still accepted", () => {
|
||||
const parsed = PaseoWorktreeArchiveRequestSchema.parse({
|
||||
type: "paseo_worktree_archive_request",
|
||||
worktreePath: "/repo/app",
|
||||
requestId: "req-extra",
|
||||
extraField: "ignored",
|
||||
});
|
||||
expect(parsed).not.toHaveProperty("extraField");
|
||||
expect(parsed.scope).toBe("workspace");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1614,10 +1614,15 @@ export const PaseoWorktreeArchiveRequestSchema = z.object({
|
||||
// present the daemon archives this exact workspace; when absent it falls back to
|
||||
// resolving by worktreePath, preferring the worktree-kind record on a cwd tie.
|
||||
workspaceId: z.string().optional(),
|
||||
// COMPAT(worktreeDiskDeletion): added in v0.1.97, drop the optional gate when floor >= v0.1.97.
|
||||
// When true, and the workspace is the last active reference to a Paseo-owned
|
||||
// worktree, the daemon also removes the worktree directory from disk. Default
|
||||
// false: archiving only removes the workspace record and leaves the directory.
|
||||
// COMPAT(worktreeArchiveScope): added in v0.1.97, drop the gate when floor >= v0.1.97.
|
||||
// Scope of the archive operation. "workspace" archives a single workspace record
|
||||
// (today's default UI behavior). "worktree" archives every active workspace whose
|
||||
// cwd resolves to the target directory, then removes the directory if it is
|
||||
// Paseo-owned. Omitted/unknown values default to "workspace" for old-client safety.
|
||||
scope: z.enum(["workspace", "worktree"]).optional().default("workspace"),
|
||||
// COMPAT(worktreeDiskDeletion): added in v0.1.97, ignored as of v0.1.97
|
||||
// (disk removal derived from scope + last-reference + ownership); field
|
||||
// retained for wire parse-compat, drop when floor >= v0.1.97.
|
||||
deleteWorktreeFromDisk: z.boolean().optional().default(false),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user