diff --git a/src/ClaudeDo.Worker/Runner/WorktreeManager.cs b/src/ClaudeDo.Worker/Runner/WorktreeManager.cs index 09aba22..e0c9561 100644 --- a/src/ClaudeDo.Worker/Runner/WorktreeManager.cs +++ b/src/ClaudeDo.Worker/Runner/WorktreeManager.cs @@ -140,4 +140,32 @@ public sealed class WorktreeManager _logger.LogInformation("Committed changes for task {TaskId}: {Head}", task.Id, head); return true; } + + public async Task DiscardAsync(WorktreeEntity wt, string workingDir, CancellationToken ct) + { + try + { + await _git.WorktreeRemoveAsync(workingDir, wt.Path, force: true, ct); + } + catch (Exception ex) + { + _logger.LogError(ex, "git worktree remove failed for {Path}", wt.Path); + throw; + } + + try + { + await _git.BranchDeleteAsync(workingDir, wt.BranchName, force: true, ct); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "git branch -D {Branch} failed after worktree removal; continuing", wt.BranchName); + } + + using var context = _dbFactory.CreateDbContext(); + var wtRepo = new WorktreeRepository(context); + await wtRepo.SetStateAsync(wt.TaskId, WorktreeState.Discarded, ct); + + _logger.LogInformation("Discarded worktree for task {TaskId} (branch {Branch})", wt.TaskId, wt.BranchName); + } } diff --git a/tests/ClaudeDo.Worker.Tests/Runner/WorktreeManagerTests.cs b/tests/ClaudeDo.Worker.Tests/Runner/WorktreeManagerTests.cs index 4e28ec9..779bf39 100644 --- a/tests/ClaudeDo.Worker.Tests/Runner/WorktreeManagerTests.cs +++ b/tests/ClaudeDo.Worker.Tests/Runner/WorktreeManagerTests.cs @@ -181,6 +181,32 @@ public class WorktreeManagerTests : IDisposable return (task, list); } + [Fact] + public async Task DiscardAsync_RemovesWorktreeAndBranch_AndSetsStateDiscarded() + { + if (!GitAvailable) { Assert.True(true, "git not available -- skipping"); return; } + + var repo = CreateRepo(); + var (task, list) = MakeEntities(repo.RepoDir); + var (mgr, db) = await CreateManagerAsync(task, list); + + var ctx = await mgr.CreateAsync(task, list, CancellationToken.None); + var worktreePath = ctx.WorktreePath; + + WorktreeEntity wt; + using (var readCtx = db.CreateContext()) + wt = (await new WorktreeRepository(readCtx).GetByTaskIdAsync(task.Id))!; + + await mgr.DiscardAsync(wt, list.WorkingDir!, CancellationToken.None); + + Assert.False(Directory.Exists(worktreePath), "worktree directory should be gone"); + + using var readCtx2 = db.CreateContext(); + var row = await new WorktreeRepository(readCtx2).GetByTaskIdAsync(task.Id); + Assert.NotNull(row); + Assert.Equal(WorktreeState.Discarded, row!.State); + } + public void Dispose() { foreach (var (repoDir, wtPath) in _worktreeCleanups)