refactor(merge): drop dead hunks conflict API
GetConflictsAsync/GetMergeConflicts (+ MergeConflicts/ConflictFileContent/ConflictFileDto/ConflictHunkDto DTOs and the now-orphaned GitService.ShowStageAsync) were superseded by the segment-based GetMergeConflictDocuments path and had no production callers. Removes the IWorkerClient member, both test fakes, the lingering test, and updates the Worker/Ui/Data CLAUDE.md surface notes.
This commit is contained in:
@@ -35,7 +35,7 @@ All repositories use EF Core LINQ queries via `ClaudeDoDbContext`. The atomic `Q
|
|||||||
|
|
||||||
## Git
|
## Git
|
||||||
|
|
||||||
- **GitService** — async wrapper around git CLI (ProcessStartInfo, no shell). Worktree ops (add — serialized to avoid a commondir race —, remove, prune, list paths for branch), branch ops (current, list local, checkout, delete), staging/commit (status porcelain, add-all, add-path, commit via stdin), diffs (working tree, branch vs base, commit range `base..head` — used to show a merged task's diff after the worktree is gone —, per-file, diff-stat, committed files, has-changes), merge (ff-only, no-ff, abort, mid-merge detection, conflicted files, show-stage for conflict hunks), `PreviewMergeAsync` (non-destructive mergeability check via `git merge-tree --write-tree`), `CountChangedFilesAsync`, rev-parse, is-git-repo
|
- **GitService** — async wrapper around git CLI (ProcessStartInfo, no shell). Worktree ops (add — serialized to avoid a commondir race —, remove, prune, list paths for branch), branch ops (current, list local, checkout, delete), staging/commit (status porcelain, add-all, add-path, commit via stdin), diffs (working tree, branch vs base, commit range `base..head` — used to show a merged task's diff after the worktree is gone —, per-file, diff-stat, committed files, has-changes), merge (ff-only, no-ff, abort, mid-merge detection, conflicted files), `PreviewMergeAsync` (non-destructive mergeability check via `git merge-tree --write-tree`), `CountChangedFilesAsync`, rev-parse, is-git-repo
|
||||||
|
|
||||||
## Schema
|
## Schema
|
||||||
|
|
||||||
|
|||||||
@@ -280,17 +280,6 @@ public sealed class GitService
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reads a conflicted file's blob at a merge stage: 1=base, 2=ours, 3=theirs.
|
|
||||||
/// Returns null when the stage doesn't exist (e.g. add/add conflict has no base).
|
|
||||||
/// Output is NOT trimmed so file content round-trips exactly.
|
|
||||||
/// </summary>
|
|
||||||
public async Task<string?> ShowStageAsync(string repoDir, int stage, string path, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
var (exitCode, stdout, _) = await RunGitAsync(repoDir, ["show", $":{stage}:{path}"], ct, trimOutput: false);
|
|
||||||
return exitCode == 0 ? stdout : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task AddPathAsync(string repoDir, string path, CancellationToken ct = default)
|
public async Task AddPathAsync(string repoDir, string path, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
var (exitCode, _, stderr) = await RunGitAsync(repoDir, ["add", "--", path], ct);
|
var (exitCode, _, stderr) = await RunGitAsync(repoDir, ["add", "--", path], ct);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ Design/ — Tokens.axaml (design tokens; merged before styles) + IslandStyle
|
|||||||
|
|
||||||
## Services
|
## Services
|
||||||
|
|
||||||
- **WorkerClient** / **IWorkerClient** — SignalR client connecting to `http://127.0.0.1:47821/hub`, auto-reconnect with exponential backoff. The surface tracks `WorkerHub` (see `src/ClaudeDo.Worker/CLAUDE.md` for the canonical method/event list); groups: task execution (RunNow/Cancel/Continue/Reset/SetTaskStatus), review (`ApproveReviewAsync(taskId, targetBranch) -> MergeResultDto`, reject-to-queue/idle, cancel review, `PreviewMergeAsync -> MergePreviewDto`), planning sessions (start/resume/discard/finalize, queue subtasks, pending draft count, interactive terminal, refine), planning aggregate/integration-branch diffs, unit-merge continue/abort, single-task conflict resolving (start/get-conflicts/write-resolution/continue/abort), worktrees (overview, set state, force remove, cleanup, reset all), agents, app settings, lists/config, weekly report, daily notes, daily prep (`RunDailyPrepNowAsync`, `ClearMyDayAsync`, `GetLastPrepLogAsync`), prime schedules. Events mirror `HubBroadcaster` (task/worktree/list/run updates, prep events, planning-merge events, refine events, worker log). Lifecycle (`StartAsync`/`StopAsync`) and a few admin methods live only on the concrete `WorkerClient`.
|
- **WorkerClient** / **IWorkerClient** — SignalR client connecting to `http://127.0.0.1:47821/hub`, auto-reconnect with exponential backoff. The surface tracks `WorkerHub` (see `src/ClaudeDo.Worker/CLAUDE.md` for the canonical method/event list); groups: task execution (RunNow/Cancel/Continue/Reset/SetTaskStatus), review (`ApproveReviewAsync(taskId, targetBranch) -> MergeResultDto`, reject-to-queue/idle, cancel review, `PreviewMergeAsync -> MergePreviewDto`), planning sessions (start/resume/discard/finalize, queue subtasks, pending draft count, interactive terminal, refine), planning aggregate/integration-branch diffs, unit-merge continue/abort, single-task conflict resolving (start/get-conflict-documents/write-resolution/continue/abort), worktrees (overview, set state, force remove, cleanup, reset all), agents, app settings, lists/config, weekly report, daily notes, daily prep (`RunDailyPrepNowAsync`, `ClearMyDayAsync`, `GetLastPrepLogAsync`), prime schedules. Events mirror `HubBroadcaster` (task/worktree/list/run updates, prep events, planning-merge events, refine events, worker log). Lifecycle (`StartAsync`/`StopAsync`) and a few admin methods live only on the concrete `WorkerClient`.
|
||||||
- **INotesApi** / **WorkerNotesApi** — daily-note CRUD (`ListAsync(day)`, `AddAsync`, `UpdateAsync`, `DeleteAsync`); UI DTO `DailyNoteDto(Id, Date, Text, SortOrder)`.
|
- **INotesApi** / **WorkerNotesApi** — daily-note CRUD (`ListAsync(day)`, `AddAsync`, `UpdateAsync`, `DeleteAsync`); UI DTO `DailyNoteDto(Id, Date, Text, SortOrder)`.
|
||||||
- **IPrimeScheduleApi** — prime-schedule CRUD (`ListAsync`, `UpsertAsync`, `DeleteAsync`).
|
- **IPrimeScheduleApi** — prime-schedule CRUD (`ListAsync`, `UpsertAsync`, `DeleteAsync`).
|
||||||
- **UpdateCheckService** — polls releases, exposes `LastCheckStatus`/`LatestVersion`/`CheckNowAsync` (feeds the shell's update banner).
|
- **UpdateCheckService** — polls releases, exposes `LastCheckStatus`/`LatestVersion`/`CheckNowAsync` (feeds the shell's update banner).
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ public interface IWorkerClient : INotifyPropertyChanged
|
|||||||
|
|
||||||
// ── Conflict resolution (worker hub side implemented by Layer C) ──
|
// ── Conflict resolution (worker hub side implemented by Layer C) ──
|
||||||
Task<MergeResultDto> StartConflictMergeAsync(string taskId, string targetBranch);
|
Task<MergeResultDto> StartConflictMergeAsync(string taskId, string targetBranch);
|
||||||
Task<MergeConflictsDto> GetMergeConflictsAsync(string taskId);
|
|
||||||
Task<MergeConflictDocumentsDto> GetMergeConflictDocumentsAsync(string taskId);
|
Task<MergeConflictDocumentsDto> GetMergeConflictDocumentsAsync(string taskId);
|
||||||
Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent);
|
Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent);
|
||||||
Task<MergeResultDto> ContinueConflictMergeAsync(string taskId);
|
Task<MergeResultDto> ContinueConflictMergeAsync(string taskId);
|
||||||
|
|||||||
@@ -272,9 +272,6 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
public Task<MergeResultDto> StartConflictMergeAsync(string taskId, string targetBranch)
|
public Task<MergeResultDto> StartConflictMergeAsync(string taskId, string targetBranch)
|
||||||
=> _hub.InvokeAsync<MergeResultDto>("StartConflictMerge", taskId, targetBranch);
|
=> _hub.InvokeAsync<MergeResultDto>("StartConflictMerge", taskId, targetBranch);
|
||||||
|
|
||||||
public Task<MergeConflictsDto> GetMergeConflictsAsync(string taskId)
|
|
||||||
=> _hub.InvokeAsync<MergeConflictsDto>("GetMergeConflicts", taskId);
|
|
||||||
|
|
||||||
public Task<MergeConflictDocumentsDto> GetMergeConflictDocumentsAsync(string taskId)
|
public Task<MergeConflictDocumentsDto> GetMergeConflictDocumentsAsync(string taskId)
|
||||||
=> _hub.InvokeAsync<MergeConflictDocumentsDto>("GetMergeConflictDocuments", taskId);
|
=> _hub.InvokeAsync<MergeConflictDocumentsDto>("GetMergeConflictDocuments", taskId);
|
||||||
|
|
||||||
@@ -559,9 +556,6 @@ public sealed record WorktreeResetDto(int Removed, int TasksAffected, bool Block
|
|||||||
public record MergeResultDto(string Status, IReadOnlyList<string> ConflictFiles, string? ErrorMessage);
|
public record MergeResultDto(string Status, IReadOnlyList<string> ConflictFiles, string? ErrorMessage);
|
||||||
public record MergePreviewDto(string Status, IReadOnlyList<string> ConflictFiles, int ChangedFileCount);
|
public record MergePreviewDto(string Status, IReadOnlyList<string> ConflictFiles, int ChangedFileCount);
|
||||||
public record MergeTargetsDto(string DefaultBranch, IReadOnlyList<string> LocalBranches);
|
public record MergeTargetsDto(string DefaultBranch, IReadOnlyList<string> LocalBranches);
|
||||||
public record MergeConflictsDto(string TaskId, IReadOnlyList<ConflictFileDto> Files);
|
|
||||||
public record ConflictFileDto(string Path, IReadOnlyList<ConflictHunkDto> Hunks);
|
|
||||||
public record ConflictHunkDto(string Ours, string Theirs, string? Base);
|
|
||||||
public record MergeConflictDocumentsDto(string TaskId, IReadOnlyList<ConflictDocumentDto> Files);
|
public record MergeConflictDocumentsDto(string TaskId, IReadOnlyList<ConflictDocumentDto> Files);
|
||||||
public record ConflictDocumentDto(string Path, bool IsBinary, IReadOnlyList<MergeSegmentDto> Segments);
|
public record ConflictDocumentDto(string Path, bool IsBinary, IReadOnlyList<MergeSegmentDto> Segments);
|
||||||
public record MergeSegmentDto(bool IsConflict, string Text, string Ours, string? Base, string Theirs);
|
public record MergeSegmentDto(bool IsConflict, string Text, string Ours, string? Base, string Theirs);
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ Each CLI invocation is recorded in the `task_runs` table via `TaskRunRepository`
|
|||||||
|
|
||||||
- Execution: `Ping`, `GetActive`, `RunNow`, `CancelTask`, `WakeQueue`, `ContinueTask`, `ResetTask`, `SetTaskStatus`, `RefineTask`
|
- Execution: `Ping`, `GetActive`, `RunNow`, `CancelTask`, `WakeQueue`, `ContinueTask`, `ResetTask`, `SetTaskStatus`, `RefineTask`
|
||||||
- Review/merge: `ApproveReview(taskId, targetBranch) -> MergeResultDto` (childless task: merges its worktree then Done, conflict stays WaitingForReview; task with children: drives `PlanningMergeOrchestrator` to merge the whole unit), `ContinuePlanningMerge` / `AbortPlanningMerge` (resolve a unit-merge conflict), `PreviewMerge(taskId, targetBranch) -> MergePreviewDto` (non-destructive mergeability check), `RejectReviewToQueue`, `RejectReviewToIdle`, `CancelReview`, `MergeTask`, `GetMergeTargets`
|
- Review/merge: `ApproveReview(taskId, targetBranch) -> MergeResultDto` (childless task: merges its worktree then Done, conflict stays WaitingForReview; task with children: drives `PlanningMergeOrchestrator` to merge the whole unit), `ContinuePlanningMerge` / `AbortPlanningMerge` (resolve a unit-merge conflict), `PreviewMerge(taskId, targetBranch) -> MergePreviewDto` (non-destructive mergeability check), `RejectReviewToQueue`, `RejectReviewToIdle`, `CancelReview`, `MergeTask`, `GetMergeTargets`
|
||||||
- Single-task conflict resolver (Layer C): `StartConflictMerge`, `GetMergeConflicts` (hunks), `WriteConflictResolution`, `ContinueConflictMerge`, `AbortConflictMerge` (service-level `TaskMergeService.ContinueMergeAsync`/`AbortMergeAsync` keep their names)
|
- Single-task conflict resolver (Layer C): `StartConflictMerge`, `GetMergeConflictDocuments` (segments), `WriteConflictResolution`, `ContinueConflictMerge`, `AbortConflictMerge` (service-level `TaskMergeService.ContinueMergeAsync`/`AbortMergeAsync` keep their names)
|
||||||
- Planning sessions: `StartPlanningSession`, `ResumePlanningSession`, `DiscardPlanningSession`, `FinalizePlanningSession`, `QueuePlanningSubtasks`, `GetPendingDraftCount`, `OpenInteractiveTerminal`, `GetPlanningAggregate` (per-subtask diffs), `BuildPlanningIntegrationBranch` (combined diff)
|
- Planning sessions: `StartPlanningSession`, `ResumePlanningSession`, `DiscardPlanningSession`, `FinalizePlanningSession`, `QueuePlanningSubtasks`, `GetPendingDraftCount`, `OpenInteractiveTerminal`, `GetPlanningAggregate` (per-subtask diffs), `BuildPlanningIntegrationBranch` (combined diff)
|
||||||
- Worktrees: `CleanupFinishedWorktrees`, `ResetAllWorktrees`, `GetWorktreesOverview`, `SetWorktreeState`, `ForceRemoveWorktree`
|
- Worktrees: `CleanupFinishedWorktrees`, `ResetAllWorktrees`, `GetWorktreesOverview`, `SetWorktreeState`, `ForceRemoveWorktree`
|
||||||
- Agents/settings/lists: `GetAgents`, `RefreshAgents`, `RestoreDefaultAgents`, `GetAppSettings`, `UpdateAppSettings`, `UpdateList`, `UpdateListConfig`, `GetListConfig`, `UpdateTaskAgentSettings`
|
- Agents/settings/lists: `GetAgents`, `RefreshAgents`, `RestoreDefaultAgents`, `GetAppSettings`, `UpdateAppSettings`, `UpdateList`, `UpdateListConfig`, `GetListConfig`, `UpdateTaskAgentSettings`
|
||||||
|
|||||||
@@ -58,9 +58,6 @@ public record ForceRemoveResultDto(bool Removed, string? Reason);
|
|||||||
public record MergeResultDto(string Status, IReadOnlyList<string> ConflictFiles, string? ErrorMessage);
|
public record MergeResultDto(string Status, IReadOnlyList<string> ConflictFiles, string? ErrorMessage);
|
||||||
public record MergePreviewDto(string Status, IReadOnlyList<string> ConflictFiles, int ChangedFileCount);
|
public record MergePreviewDto(string Status, IReadOnlyList<string> ConflictFiles, int ChangedFileCount);
|
||||||
public record MergeTargetsDto(string DefaultBranch, IReadOnlyList<string> LocalBranches);
|
public record MergeTargetsDto(string DefaultBranch, IReadOnlyList<string> LocalBranches);
|
||||||
public record MergeConflictsDto(string TaskId, IReadOnlyList<ConflictFileDto> Files);
|
|
||||||
public record ConflictFileDto(string Path, IReadOnlyList<ConflictHunkDto> Hunks);
|
|
||||||
public record ConflictHunkDto(string Ours, string Theirs, string? Base);
|
|
||||||
public record MergeConflictDocumentsDto(string TaskId, IReadOnlyList<ConflictDocumentDto> Files);
|
public record MergeConflictDocumentsDto(string TaskId, IReadOnlyList<ConflictDocumentDto> Files);
|
||||||
public record ConflictDocumentDto(string Path, bool IsBinary, IReadOnlyList<MergeSegmentDto> Segments);
|
public record ConflictDocumentDto(string Path, bool IsBinary, IReadOnlyList<MergeSegmentDto> Segments);
|
||||||
public record MergeSegmentDto(bool IsConflict, string Text, string Ours, string? Base, string Theirs);
|
public record MergeSegmentDto(bool IsConflict, string Text, string Ours, string? Base, string Theirs);
|
||||||
@@ -375,17 +372,6 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub
|
|||||||
return new MergeResultDto(r.Status, r.ConflictFiles, r.ErrorMessage);
|
return new MergeResultDto(r.Status, r.ConflictFiles, r.ErrorMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
public Task<MergeConflictsDto> GetMergeConflicts(string taskId)
|
|
||||||
=> HubGuard(async () =>
|
|
||||||
{
|
|
||||||
var c = await _mergeService.GetConflictsAsync(taskId, CancellationToken.None);
|
|
||||||
return new MergeConflictsDto(
|
|
||||||
c.TaskId,
|
|
||||||
c.Files.Select(f => new ConflictFileDto(
|
|
||||||
f.Path,
|
|
||||||
new[] { new ConflictHunkDto(f.Ours, f.Theirs, f.Base) })).ToList());
|
|
||||||
});
|
|
||||||
|
|
||||||
public Task<MergeConflictDocumentsDto> GetMergeConflictDocuments(string taskId)
|
public Task<MergeConflictDocumentsDto> GetMergeConflictDocuments(string taskId)
|
||||||
=> HubGuard(async () =>
|
=> HubGuard(async () =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,16 +23,6 @@ public sealed record MergePreviewResult(
|
|||||||
IReadOnlyList<string> ConflictFiles,
|
IReadOnlyList<string> ConflictFiles,
|
||||||
int ChangedFileCount);
|
int ChangedFileCount);
|
||||||
|
|
||||||
public sealed record MergeConflicts(
|
|
||||||
string TaskId,
|
|
||||||
IReadOnlyList<ConflictFileContent> Files);
|
|
||||||
|
|
||||||
public sealed record ConflictFileContent(
|
|
||||||
string Path,
|
|
||||||
string Ours,
|
|
||||||
string Theirs,
|
|
||||||
string? Base);
|
|
||||||
|
|
||||||
public sealed record ConflictDocuments(
|
public sealed record ConflictDocuments(
|
||||||
string TaskId,
|
string TaskId,
|
||||||
IReadOnlyList<ConflictDocumentContent> Files);
|
IReadOnlyList<ConflictDocumentContent> Files);
|
||||||
@@ -247,24 +237,6 @@ public sealed class TaskMergeService
|
|||||||
return new MergeResult(StatusAborted, Array.Empty<string>(), null);
|
return new MergeResult(StatusAborted, Array.Empty<string>(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<MergeConflicts> GetConflictsAsync(string taskId, CancellationToken ct)
|
|
||||||
{
|
|
||||||
var (_, list, _) = await LoadMergeContextAsync(taskId, ct);
|
|
||||||
if (string.IsNullOrWhiteSpace(list.WorkingDir))
|
|
||||||
throw new InvalidOperationException("list has no working directory");
|
|
||||||
|
|
||||||
var files = await _git.ListConflictedFilesAsync(list.WorkingDir, ct);
|
|
||||||
var result = new List<ConflictFileContent>(files.Count);
|
|
||||||
foreach (var path in files)
|
|
||||||
{
|
|
||||||
var ours = await _git.ShowStageAsync(list.WorkingDir, 2, path, ct) ?? "";
|
|
||||||
var theirs = await _git.ShowStageAsync(list.WorkingDir, 3, path, ct) ?? "";
|
|
||||||
var @base = await _git.ShowStageAsync(list.WorkingDir, 1, path, ct);
|
|
||||||
result.Add(new ConflictFileContent(path, ours, theirs, @base));
|
|
||||||
}
|
|
||||||
return new MergeConflicts(taskId, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads each conflicted working-tree file and parses its conflict markers into line-level
|
/// Reads each conflicted working-tree file and parses its conflict markers into line-level
|
||||||
/// segments (with the diff3 merge base when present). Binary files are flagged and skipped.
|
/// segments (with the diff3 merge base when present). Binary files are flagged and skipped.
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ public abstract class StubWorkerClient : IWorkerClient
|
|||||||
public virtual Task RejectReviewToIdleAsync(string taskId) => Task.CompletedTask;
|
public virtual Task RejectReviewToIdleAsync(string taskId) => Task.CompletedTask;
|
||||||
public virtual Task CancelReviewAsync(string taskId) => Task.CompletedTask;
|
public virtual Task CancelReviewAsync(string taskId) => Task.CompletedTask;
|
||||||
public virtual Task<MergeResultDto> StartConflictMergeAsync(string taskId, string targetBranch) => Task.FromResult(new MergeResultDto("conflict", System.Array.Empty<string>(), null));
|
public virtual Task<MergeResultDto> StartConflictMergeAsync(string taskId, string targetBranch) => Task.FromResult(new MergeResultDto("conflict", System.Array.Empty<string>(), null));
|
||||||
public virtual Task<MergeConflictsDto> GetMergeConflictsAsync(string taskId) => Task.FromResult(new MergeConflictsDto(taskId, System.Array.Empty<ConflictFileDto>()));
|
|
||||||
public virtual Task<MergeConflictDocumentsDto> GetMergeConflictDocumentsAsync(string taskId) => Task.FromResult(new MergeConflictDocumentsDto(taskId, System.Array.Empty<ConflictDocumentDto>()));
|
public virtual Task<MergeConflictDocumentsDto> GetMergeConflictDocumentsAsync(string taskId) => Task.FromResult(new MergeConflictDocumentsDto(taskId, System.Array.Empty<ConflictDocumentDto>()));
|
||||||
public virtual Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent) => Task.CompletedTask;
|
public virtual Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent) => Task.CompletedTask;
|
||||||
public virtual Task<MergeResultDto> ContinueConflictMergeAsync(string taskId) => Task.FromResult(new MergeResultDto("merged", System.Array.Empty<string>(), null));
|
public virtual Task<MergeResultDto> ContinueConflictMergeAsync(string taskId) => Task.FromResult(new MergeResultDto("merged", System.Array.Empty<string>(), null));
|
||||||
|
|||||||
@@ -668,41 +668,6 @@ public class TaskMergeServiceTests : IDisposable
|
|||||||
// Cleanup
|
// Cleanup
|
||||||
GitRepoFixture.RunGit(repo.RepoDir, "merge", "--abort");
|
GitRepoFixture.RunGit(repo.RepoDir, "merge", "--abort");
|
||||||
}
|
}
|
||||||
[Fact]
|
|
||||||
public async Task GetConflictsAsync_AfterConflictMerge_ReturnsOursAndTheirs()
|
|
||||||
{
|
|
||||||
if (!GitRepoFixture.IsGitAvailable()) return;
|
|
||||||
|
|
||||||
var db = NewDb();
|
|
||||||
var repo = NewRepo();
|
|
||||||
GitRepoFixture.RunGit(repo.RepoDir, "branch", "-m", "main");
|
|
||||||
File.WriteAllText(Path.Combine(repo.RepoDir, "README.md"), "# main change\n");
|
|
||||||
GitRepoFixture.RunGit(repo.RepoDir, "commit", "-am", "main change");
|
|
||||||
|
|
||||||
var wtPath = Path.Combine(Path.GetTempPath(), $"wt_{Guid.NewGuid():N}");
|
|
||||||
_wtCleanups.Add((repo.RepoDir, wtPath));
|
|
||||||
GitRepoFixture.RunGit(repo.RepoDir, "worktree", "add", "-b", "claudedo/c1", wtPath, repo.BaseCommit);
|
|
||||||
File.WriteAllText(Path.Combine(wtPath, "README.md"), "# branch change\n");
|
|
||||||
GitRepoFixture.RunGit(wtPath, "commit", "-am", "branch change");
|
|
||||||
|
|
||||||
var (_, task) = await SeedListAndTask(db, workingDir: repo.RepoDir, status: TaskStatus.WaitingForReview);
|
|
||||||
await SeedWorktree(db, task.Id, wtPath, "claudedo/c1", repo.BaseCommit);
|
|
||||||
|
|
||||||
var (svc, _) = BuildService(db);
|
|
||||||
var start = await svc.MergeAsync(task.Id, "main", false, "msg", leaveConflictsInTree: true, CancellationToken.None);
|
|
||||||
Assert.Equal(TaskMergeService.StatusConflict, start.Status);
|
|
||||||
|
|
||||||
var conflicts = await svc.GetConflictsAsync(task.Id, CancellationToken.None);
|
|
||||||
|
|
||||||
Assert.Equal(task.Id, conflicts.TaskId);
|
|
||||||
var file = Assert.Single(conflicts.Files);
|
|
||||||
Assert.Equal("README.md", file.Path);
|
|
||||||
Assert.Contains("main change", file.Ours);
|
|
||||||
Assert.Contains("branch change", file.Theirs);
|
|
||||||
Assert.NotNull(file.Base);
|
|
||||||
|
|
||||||
GitRepoFixture.RunGit(repo.RepoDir, "merge", "--abort");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task WriteResolutionAsync_ThenContinue_CompletesMerge()
|
public async Task WriteResolutionAsync_ThenContinue_CompletesMerge()
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ sealed class FakeWorkerClient : IWorkerClient
|
|||||||
public Task<MergePreviewDto?> PreviewMergeAsync(string taskId, string targetBranch) => Task.FromResult<MergePreviewDto?>(null);
|
public Task<MergePreviewDto?> PreviewMergeAsync(string taskId, string targetBranch) => Task.FromResult<MergePreviewDto?>(null);
|
||||||
public Task<MergeResultDto> MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage) => Task.FromResult(new MergeResultDto("merged", System.Array.Empty<string>(), null));
|
public Task<MergeResultDto> MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage) => Task.FromResult(new MergeResultDto("merged", System.Array.Empty<string>(), null));
|
||||||
public Task<MergeResultDto> StartConflictMergeAsync(string taskId, string targetBranch) => Task.FromResult(new MergeResultDto("conflict", System.Array.Empty<string>(), null));
|
public Task<MergeResultDto> StartConflictMergeAsync(string taskId, string targetBranch) => Task.FromResult(new MergeResultDto("conflict", System.Array.Empty<string>(), null));
|
||||||
public Task<MergeConflictsDto> GetMergeConflictsAsync(string taskId) => Task.FromResult(new MergeConflictsDto(taskId, System.Array.Empty<ConflictFileDto>()));
|
|
||||||
public Task<MergeConflictDocumentsDto> GetMergeConflictDocumentsAsync(string taskId) => Task.FromResult(new MergeConflictDocumentsDto(taskId, System.Array.Empty<ConflictDocumentDto>()));
|
public Task<MergeConflictDocumentsDto> GetMergeConflictDocumentsAsync(string taskId) => Task.FromResult(new MergeConflictDocumentsDto(taskId, System.Array.Empty<ConflictDocumentDto>()));
|
||||||
public Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent) => Task.CompletedTask;
|
public Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent) => Task.CompletedTask;
|
||||||
public Task<MergeResultDto> ContinueConflictMergeAsync(string taskId) => Task.FromResult(new MergeResultDto("merged", System.Array.Empty<string>(), null));
|
public Task<MergeResultDto> ContinueConflictMergeAsync(string taskId) => Task.FromResult(new MergeResultDto("merged", System.Array.Empty<string>(), null));
|
||||||
|
|||||||
Reference in New Issue
Block a user