Skip to content

Commit 7e0f192

Browse files
committed
fix: force remove worktrees from backend
1 parent 4ad6a73 commit 7e0f192

2 files changed

Lines changed: 11 additions & 28 deletions

File tree

backend/src/__tests__/lifecycle-service.test.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ describe("LifecycleService", () => {
479479
]);
480480
});
481481

482-
it("rejects removing a dirty worktree", async () => {
482+
it("force removes a dirty worktree", async () => {
483483
const repoRoot = await initRepo();
484484
const runtime = new ProjectRuntime();
485485
const tmux = new FakeTmuxGateway();
@@ -491,16 +491,14 @@ describe("LifecycleService", () => {
491491
const worktreePath = join(repoRoot, "__worktrees", "feature-dirty");
492492
await Bun.write(join(worktreePath, "README.md"), "# dirty\n");
493493

494-
await expect(lifecycle.removeWorktree("feature-dirty")).rejects.toThrow(
495-
"Worktree has uncommitted changes: feature-dirty",
496-
);
494+
await lifecycle.removeWorktree("feature-dirty");
497495

498-
expect(hooks.calls.filter((call) => call.name === "preRemove")).toHaveLength(0);
499-
expect(new BunGitGateway().listWorktrees(repoRoot).some((entry) => entry.path === worktreePath)).toBe(true);
500-
expect(run(["git", "branch", "--list", "feature-dirty"], repoRoot)).toContain("feature-dirty");
496+
expect(hooks.calls.filter((call) => call.name === "preRemove")).toHaveLength(1);
497+
expect(new BunGitGateway().listWorktrees(repoRoot).some((entry) => entry.path === worktreePath)).toBe(false);
498+
expect(run(["git", "branch", "--list", "feature-dirty"], repoRoot)).toBe("");
501499
});
502500

503-
it("rejects removing a clean worktree that is ahead of its upstream", async () => {
501+
it("force removes a worktree that is ahead of its upstream", async () => {
504502
const repoRoot = await initRepo();
505503
const runtime = new ProjectRuntime();
506504
const tmux = new FakeTmuxGateway();
@@ -518,12 +516,10 @@ describe("LifecycleService", () => {
518516

519517
await lifecycle.createWorktree({ branch: "feature-ahead" });
520518

521-
await expect(lifecycle.removeWorktree("feature-ahead")).rejects.toThrow(
522-
"Worktree has unpushed commits: feature-ahead",
523-
);
519+
await lifecycle.removeWorktree("feature-ahead");
524520

525-
expect(hooks.calls.filter((call) => call.name === "preRemove")).toHaveLength(0);
526-
expect(run(["git", "branch", "--list", "feature-ahead"], repoRoot)).toContain("feature-ahead");
521+
expect(hooks.calls.filter((call) => call.name === "preRemove")).toHaveLength(1);
522+
expect(run(["git", "branch", "--list", "feature-ahead"], repoRoot)).toBe("");
527523
});
528524

529525
it("removes the sandbox container before deleting a docker worktree", async () => {

backend/src/services/lifecycle-service.ts

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ export class LifecycleService {
209209
async removeWorktree(branch: string): Promise<void> {
210210
try {
211211
const resolved = await this.resolveExistingWorktree(branch);
212-
await this.removeResolvedWorktree(resolved, false);
212+
await this.removeResolvedWorktree(resolved);
213213
} catch (error) {
214214
throw this.wrapOperationError(error);
215215
}
@@ -230,7 +230,7 @@ export class LifecycleService {
230230
);
231231

232232
try {
233-
await this.removeResolvedWorktree(resolved, true);
233+
await this.removeResolvedWorktree(resolved);
234234
} catch (error) {
235235
throw new LifecycleError(
236236
`Merged ${branch} into ${this.deps.config.workspace.mainBranch} but cleanup failed: ${toErrorMessage(error)}`,
@@ -541,26 +541,13 @@ export class LifecycleService {
541541
}
542542
}
543543

544-
private ensureNoAheadCommits(entry: GitWorktreeEntry): void {
545-
const status = this.deps.git.readWorktreeStatus(entry.path);
546-
if (status.aheadCount > 0) {
547-
throw new LifecycleError(`Worktree has unpushed commits: ${entry.branch ?? entry.path}`, 409);
548-
}
549-
}
550-
551544
private controlUrl(): string {
552545
return `${this.deps.controlBaseUrl.replace(/\/+$/, "")}/api/runtime/events`;
553546
}
554547

555548
private async removeResolvedWorktree(
556549
resolved: ResolvedLifecycleWorktree,
557-
allowAheadCommits: boolean,
558550
): Promise<void> {
559-
this.ensureNoUncommittedChanges(resolved.entry);
560-
if (!allowAheadCommits) {
561-
this.ensureNoAheadCommits(resolved.entry);
562-
}
563-
564551
await this.runLifecycleHook({
565552
name: "preRemove",
566553
command: this.deps.config.lifecycleHooks.preRemove,

0 commit comments

Comments
 (0)