feat(worker): approve merges worktree before marking task done
This commit is contained in:
@@ -3,6 +3,7 @@ using ClaudeDo.Data.Git;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Data.Repositories;
|
||||
using ClaudeDo.Worker.Hub;
|
||||
using ClaudeDo.Worker.State;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
|
||||
@@ -17,6 +18,11 @@ public sealed record MergeTargets(
|
||||
string DefaultBranch,
|
||||
IReadOnlyList<string> LocalBranches);
|
||||
|
||||
public sealed record MergePreviewResult(
|
||||
string Status,
|
||||
IReadOnlyList<string> ConflictFiles,
|
||||
int ChangedFileCount);
|
||||
|
||||
public sealed class TaskMergeService
|
||||
{
|
||||
public const string StatusMerged = "merged";
|
||||
@@ -24,20 +30,27 @@ public sealed class TaskMergeService
|
||||
public const string StatusBlocked = "blocked";
|
||||
public const string StatusAborted = "aborted";
|
||||
|
||||
public const string PreviewClean = "clean";
|
||||
public const string PreviewConflict = "conflict";
|
||||
public const string PreviewUnavailable = "unavailable";
|
||||
|
||||
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
|
||||
private readonly GitService _git;
|
||||
private readonly HubBroadcaster _broadcaster;
|
||||
private readonly ITaskStateService _state;
|
||||
private readonly ILogger<TaskMergeService> _logger;
|
||||
|
||||
public TaskMergeService(
|
||||
IDbContextFactory<ClaudeDoDbContext> dbFactory,
|
||||
GitService git,
|
||||
HubBroadcaster broadcaster,
|
||||
ITaskStateService state,
|
||||
ILogger<TaskMergeService> logger)
|
||||
{
|
||||
_dbFactory = dbFactory;
|
||||
_git = git;
|
||||
_broadcaster = broadcaster;
|
||||
_state = state;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -216,6 +229,56 @@ public sealed class TaskMergeService
|
||||
return new MergeTargets(current, branches);
|
||||
}
|
||||
|
||||
public async Task<MergePreviewResult> PreviewAsync(string taskId, string targetBranch, CancellationToken ct)
|
||||
{
|
||||
var (_, list, wt) = await LoadMergeContextAsync(taskId, ct);
|
||||
|
||||
if (wt is null || wt.State != WorktreeState.Active)
|
||||
return new MergePreviewResult(PreviewUnavailable, Array.Empty<string>(), 0);
|
||||
if (string.IsNullOrWhiteSpace(list.WorkingDir) || !await _git.IsGitRepoAsync(list.WorkingDir, ct))
|
||||
return new MergePreviewResult(PreviewUnavailable, Array.Empty<string>(), 0);
|
||||
|
||||
var target = string.IsNullOrWhiteSpace(targetBranch)
|
||||
? await _git.GetCurrentBranchAsync(list.WorkingDir, ct)
|
||||
: targetBranch;
|
||||
|
||||
var preview = await _git.PreviewMergeAsync(list.WorkingDir, target, wt.BranchName, ct);
|
||||
if (!preview.Supported)
|
||||
return new MergePreviewResult(PreviewUnavailable, Array.Empty<string>(), 0);
|
||||
if (!preview.Clean)
|
||||
return new MergePreviewResult(PreviewConflict, preview.ConflictFiles, 0);
|
||||
|
||||
var count = await _git.CountChangedFilesAsync(list.WorkingDir, target, wt.BranchName, ct);
|
||||
return new MergePreviewResult(PreviewClean, Array.Empty<string>(), count);
|
||||
}
|
||||
|
||||
public async Task<MergeResult> ApproveAndMergeAsync(string taskId, string targetBranch, CancellationToken ct)
|
||||
{
|
||||
var (task, list, wt) = await LoadMergeContextAsync(taskId, ct);
|
||||
|
||||
if (task.Status != TaskStatus.WaitingForReview)
|
||||
return Blocked("task is not waiting for review");
|
||||
|
||||
if (wt is null || wt.State != WorktreeState.Active)
|
||||
{
|
||||
var done = await _state.ApproveReviewAsync(taskId, ct);
|
||||
return done.Ok
|
||||
? new MergeResult(StatusMerged, Array.Empty<string>(), null)
|
||||
: Blocked(done.Reason ?? "approve failed");
|
||||
}
|
||||
|
||||
var target = string.IsNullOrWhiteSpace(targetBranch)
|
||||
? await _git.GetCurrentBranchAsync(list.WorkingDir, ct)
|
||||
: targetBranch;
|
||||
|
||||
var merge = await MergeAsync(taskId, target, removeWorktree: false, $"Merge {wt.BranchName}", ct);
|
||||
if (merge.Status != StatusMerged)
|
||||
return merge;
|
||||
|
||||
var approve = await _state.ApproveReviewAsync(taskId, ct);
|
||||
return approve.Ok ? merge : Blocked(approve.Reason ?? "approve failed");
|
||||
}
|
||||
|
||||
private static MergeResult Blocked(string reason) =>
|
||||
new(StatusBlocked, Array.Empty<string>(), reason);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user