deepspace 0.9.2 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/cli.js +110 -19
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/dist/cli.js
CHANGED
|
@@ -10305,6 +10305,36 @@ function inOwnLinkedWorktree(appDir, id) {
|
|
|
10305
10305
|
return false;
|
|
10306
10306
|
}
|
|
10307
10307
|
}
|
|
10308
|
+
function inspectWorkspaceCleanup(appDir, id) {
|
|
10309
|
+
const branch = workspaceBranchName(id);
|
|
10310
|
+
const branchOid = resolveCommit(appDir, `refs/heads/${branch}`);
|
|
10311
|
+
const worktrees = listWorktrees(appDir);
|
|
10312
|
+
const checkoutIndex = worktrees.findIndex((worktree) => worktree.branch === branch);
|
|
10313
|
+
if (checkoutIndex < 0) {
|
|
10314
|
+
return {
|
|
10315
|
+
branch,
|
|
10316
|
+
branchOid,
|
|
10317
|
+
checkout: { kind: "none", dir: null },
|
|
10318
|
+
willDeleteBranch: branchOid !== null
|
|
10319
|
+
};
|
|
10320
|
+
}
|
|
10321
|
+
const dir = worktrees[checkoutIndex].path;
|
|
10322
|
+
if (checkoutIndex === 0) {
|
|
10323
|
+
return {
|
|
10324
|
+
branch,
|
|
10325
|
+
branchOid,
|
|
10326
|
+
checkout: { kind: "primary", dir },
|
|
10327
|
+
willDeleteBranch: branchOid !== null
|
|
10328
|
+
};
|
|
10329
|
+
}
|
|
10330
|
+
const managed = isManagedWorkspaceWorktree(dir, id);
|
|
10331
|
+
return {
|
|
10332
|
+
branch,
|
|
10333
|
+
branchOid,
|
|
10334
|
+
checkout: { kind: managed ? "managed-linked" : "external-linked", dir },
|
|
10335
|
+
willDeleteBranch: branchOid !== null && managed
|
|
10336
|
+
};
|
|
10337
|
+
}
|
|
10308
10338
|
function cleanupJson(outcome) {
|
|
10309
10339
|
return {
|
|
10310
10340
|
worktreeRemoved: outcome.worktreeRemoved,
|
|
@@ -10314,11 +10344,27 @@ function cleanupJson(outcome) {
|
|
|
10314
10344
|
...outcome.error ? { error: outcome.error } : {}
|
|
10315
10345
|
};
|
|
10316
10346
|
}
|
|
10317
|
-
function deleteWsBranch(cwd, branch, out) {
|
|
10318
|
-
const
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10347
|
+
function deleteWsBranch(cwd, branch, expectedOid, out) {
|
|
10348
|
+
const ref = `refs/heads/${branch}`;
|
|
10349
|
+
const currentOid = resolveCommit(cwd, ref);
|
|
10350
|
+
if (!currentOid) return;
|
|
10351
|
+
const approvedOid = expectedOid ?? currentOid;
|
|
10352
|
+
if (currentOid !== approvedOid) {
|
|
10353
|
+
out.error ??= `branch ${branch} advanced from ${approvedOid.slice(0, 10)} to ${currentOid.slice(0, 10)} during cleanup; retained the branch`;
|
|
10354
|
+
return;
|
|
10355
|
+
}
|
|
10356
|
+
const del3 = runGit(cwd, ["update-ref", "-d", ref, approvedOid], { allowFail: true });
|
|
10357
|
+
if (del3.status === 0) {
|
|
10358
|
+
out.branchDeleted = branch;
|
|
10359
|
+
runGit(cwd, ["config", "--remove-section", `branch.${branch}`], { allowFail: true });
|
|
10360
|
+
return;
|
|
10361
|
+
}
|
|
10362
|
+
const afterOid = resolveCommit(cwd, ref);
|
|
10363
|
+
if (afterOid && afterOid !== approvedOid) {
|
|
10364
|
+
out.error ??= `branch ${branch} advanced from ${approvedOid.slice(0, 10)} to ${afterOid.slice(0, 10)} during cleanup; retained the branch`;
|
|
10365
|
+
} else if (afterOid) {
|
|
10366
|
+
out.error ??= `could not delete branch ${branch}: ${del3.stderr.toString("utf-8").trim() || "unknown git error"}`;
|
|
10367
|
+
}
|
|
10322
10368
|
}
|
|
10323
10369
|
function switchOffWsBranch(mainDir, trunkBranch) {
|
|
10324
10370
|
if (trunkBranch) {
|
|
@@ -10332,8 +10378,10 @@ function switchOffWsBranch(mainDir, trunkBranch) {
|
|
|
10332
10378
|
}
|
|
10333
10379
|
return runGit(mainDir, ["switch", "--quiet", "--detach"], { allowFail: true }).status === 0;
|
|
10334
10380
|
}
|
|
10335
|
-
function cleanupWorkspaceLocal(appDir, id, trunkBranch) {
|
|
10336
|
-
const
|
|
10381
|
+
function cleanupWorkspaceLocal(appDir, id, trunkBranch, options = {}) {
|
|
10382
|
+
const inspection = inspectWorkspaceCleanup(appDir, id);
|
|
10383
|
+
const { branch } = inspection;
|
|
10384
|
+
const expectedBranchOid = options.expectedBranchOid ?? inspection.branchOid;
|
|
10337
10385
|
const out = {
|
|
10338
10386
|
worktreeRemoved: null,
|
|
10339
10387
|
worktreeRetained: null,
|
|
@@ -10341,6 +10389,10 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch) {
|
|
|
10341
10389
|
worktreeDir: null,
|
|
10342
10390
|
branch
|
|
10343
10391
|
};
|
|
10392
|
+
if (options.retainLocal) {
|
|
10393
|
+
out.worktreeRetained = inspection.checkout.dir;
|
|
10394
|
+
return out;
|
|
10395
|
+
}
|
|
10344
10396
|
try {
|
|
10345
10397
|
const worktrees = listWorktrees(appDir);
|
|
10346
10398
|
const mainDir = worktrees[0]?.path ?? repoToplevel(appDir);
|
|
@@ -10367,14 +10419,15 @@ function cleanupWorkspaceLocal(appDir, id, trunkBranch) {
|
|
|
10367
10419
|
return out;
|
|
10368
10420
|
}
|
|
10369
10421
|
out.worktreeRemoved = linked.path;
|
|
10370
|
-
|
|
10422
|
+
out.worktreeDir = null;
|
|
10423
|
+
deleteWsBranch(mainDir, branch, expectedBranchOid, out);
|
|
10371
10424
|
return out;
|
|
10372
10425
|
}
|
|
10373
10426
|
if (currentBranch(mainDir) === branch && !switchOffWsBranch(mainDir, trunkBranch)) {
|
|
10374
10427
|
out.error = `could not switch off ${branch} to delete it`;
|
|
10375
10428
|
return out;
|
|
10376
10429
|
}
|
|
10377
|
-
|
|
10430
|
+
deleteWsBranch(mainDir, branch, expectedBranchOid, out);
|
|
10378
10431
|
return out;
|
|
10379
10432
|
} catch (err) {
|
|
10380
10433
|
out.error = err instanceof Error ? err.message : String(err);
|
|
@@ -11344,7 +11397,7 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11344
11397
|
}
|
|
11345
11398
|
await api4.landWorkspace(id, { landedOid, intoRef });
|
|
11346
11399
|
spinner2?.stop(`Landed ${id} into ${intoBranch} at ${landedOid.slice(0, 10)}.`);
|
|
11347
|
-
const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, intoBranch);
|
|
11400
|
+
const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, intoBranch, { expectedBranchOid: landedOid });
|
|
11348
11401
|
const inOwnWorktree = !cleanup && inOwnLinkedWorktree(appDir, id);
|
|
11349
11402
|
const retainedWorktree = cleanup?.worktreeRetained ?? (keepWorktree || inOwnWorktree ? appDir : null);
|
|
11350
11403
|
const data = {
|
|
@@ -11381,6 +11434,24 @@ var landWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11381
11434
|
|
|
11382
11435
|
// src/cli/commands/workspace/drop.ts
|
|
11383
11436
|
import * as p32 from "@clack/prompts";
|
|
11437
|
+
function isWorkspaceTipPublished(appDir, localTip, publishedOids) {
|
|
11438
|
+
return publishedOids.some(
|
|
11439
|
+
(publishedOid) => publishedOid === localTip || isAncestor(appDir, localTip, publishedOid)
|
|
11440
|
+
);
|
|
11441
|
+
}
|
|
11442
|
+
function workspaceUnsyncedRefusal(args) {
|
|
11443
|
+
const syncArgv = ["deepspace", "workspace", "sync", "--app", args.appId, "--workspace", args.id];
|
|
11444
|
+
const dropArgv = ["deepspace", "workspace", "drop", args.id, "--app", args.appId];
|
|
11445
|
+
const publish2 = args.workspaceDir ? `Publish them first with \`${humanCommand(syncArgv)}\`, then re-run \`${humanCommand(dropArgv)}\`.` : `Recreate a worktree from the branch (\`git worktree add <dir> ${args.branch}\`), publish from there with \`${humanCommand(syncArgv)}\`, then re-run \`${humanCommand(dropArgv)}\`.`;
|
|
11446
|
+
return new Refusal(
|
|
11447
|
+
`${args.id} has committed work that isn't published yet. ${publish2} Alternatively, pass \`--keep-worktree\` to drop the cloud workspace while retaining the local branch/worktree for manual disposal. Nothing was dropped.`,
|
|
11448
|
+
"workspace_unsynced",
|
|
11449
|
+
{
|
|
11450
|
+
...args.workspaceDir ? { action: { cwd: args.workspaceDir, argv: syncArgv } } : {},
|
|
11451
|
+
extra: { workspaceId: args.id }
|
|
11452
|
+
}
|
|
11453
|
+
);
|
|
11454
|
+
}
|
|
11384
11455
|
var dropWorkspaceCommand = defineDeepspaceCommand({
|
|
11385
11456
|
meta: { name: "drop", description: "Abandon a workspace (ref retained briefly, then GC)" },
|
|
11386
11457
|
args: {
|
|
@@ -11414,24 +11485,44 @@ var dropWorkspaceCommand = defineDeepspaceCommand({
|
|
|
11414
11485
|
}
|
|
11415
11486
|
return { data: { workspaceId: explicitId, status: view2.workspace.status } };
|
|
11416
11487
|
}
|
|
11417
|
-
const { appDir, api: api4 } = await resolveTarget2(appArg);
|
|
11488
|
+
const { appDir, appId, api: api4 } = await resolveTarget2(appArg);
|
|
11418
11489
|
const id = inferWorkspaceId(appDir, explicitId);
|
|
11419
|
-
const
|
|
11420
|
-
|
|
11421
|
-
if (!keepWorktree &&
|
|
11422
|
-
const
|
|
11490
|
+
const inspection = inspectWorkspaceCleanup(appDir, id);
|
|
11491
|
+
let approvedBranchOid;
|
|
11492
|
+
if (!keepWorktree && inspection.willDeleteBranch && inspection.branchOid) {
|
|
11493
|
+
const { view: before } = await api4.getWorkspace(id);
|
|
11494
|
+
const publishedOids = [
|
|
11495
|
+
before.tipOid,
|
|
11496
|
+
before.workspace.landedOid,
|
|
11497
|
+
before.workspace.baseOid
|
|
11498
|
+
].filter((oid) => oid !== null);
|
|
11499
|
+
if (!isWorkspaceTipPublished(appDir, inspection.branchOid, publishedOids)) {
|
|
11500
|
+
throw workspaceUnsyncedRefusal({
|
|
11501
|
+
appId,
|
|
11502
|
+
id,
|
|
11503
|
+
branch: inspection.branch,
|
|
11504
|
+
workspaceDir: inspection.checkout.dir
|
|
11505
|
+
});
|
|
11506
|
+
}
|
|
11507
|
+
approvedBranchOid = inspection.branchOid;
|
|
11508
|
+
}
|
|
11509
|
+
const cleanupDir = inspection.willDeleteBranch ? inspection.checkout.dir : null;
|
|
11510
|
+
if (!keepWorktree && cleanupDir && !isWorkTreeClean(cleanupDir)) {
|
|
11511
|
+
const dropArgv = ["deepspace", "workspace", "drop", id, "--app", appId];
|
|
11423
11512
|
const dropNext = humanCommand(dropArgv);
|
|
11424
11513
|
throw new Refusal(
|
|
11425
|
-
`The
|
|
11514
|
+
`The workspace checkout has uncommitted changes and cleanup would remove it or switch it off ${inspection.branch}. Commit them and re-run \`${dropNext}\`, or pass \`--keep-worktree\` to drop while retaining the checkout. Nothing was dropped.`,
|
|
11426
11515
|
"dirty_worktree",
|
|
11427
11516
|
{ extra: { workspaceId: id } }
|
|
11428
11517
|
);
|
|
11429
11518
|
}
|
|
11430
11519
|
const { view } = await api4.dropWorkspace(id);
|
|
11431
11520
|
spinner2?.message(`Cleaning up ${id} locally\u2026`);
|
|
11432
|
-
const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, null
|
|
11433
|
-
|
|
11434
|
-
|
|
11521
|
+
const cleanup = keepWorktree ? null : cleanupWorkspaceLocal(appDir, id, null, {
|
|
11522
|
+
expectedBranchOid: approvedBranchOid,
|
|
11523
|
+
retainLocal: !inspection.willDeleteBranch
|
|
11524
|
+
});
|
|
11525
|
+
const retainedWorktree = cleanup?.worktreeRetained ?? (keepWorktree ? inspection.checkout.dir : null);
|
|
11435
11526
|
const data = {
|
|
11436
11527
|
workspaceId: id,
|
|
11437
11528
|
status: view.workspace.status,
|