fix(merge): pass the stash consent to the conflict resolver so it opens on a dirty target
The resolver restarts the merge via StartConflictMerge, which had no autostash. After a stash-retry approve reported 'conflict', the restart hit the same dirty target, got 'dirty_target' and the resolver silently never opened. Thread the consent through the hub, client, coordinator and resolver; surface a refusal in the footer instead of dropping it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
ff7197c410
commit
feb0afa33a
@@ -1567,6 +1567,50 @@ public class TaskMergeServiceTests : IDisposable
|
||||
GitRepoFixture.RunGit(repo.RepoDir, "merge", "--abort");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MergeAsync_LeaveConflicts_DirtyTarget_NeedsAutostashAndAbortRestoresTheChanges()
|
||||
{
|
||||
// The resolver restarts the merge via leaveConflictsInTree; on a dirty target that only
|
||||
// works with the caller's stash consent, and git hands the stash back on abort.
|
||||
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");
|
||||
File.WriteAllText(Path.Combine(repo.RepoDir, "other.txt"), "tracked\n");
|
||||
GitRepoFixture.RunGit(repo.RepoDir, "add", "-A");
|
||||
GitRepoFixture.RunGit(repo.RepoDir, "commit", "-m", "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/t1", 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/t1", repo.BaseCommit);
|
||||
|
||||
File.WriteAllText(Path.Combine(repo.RepoDir, "other.txt"), "dirty\n");
|
||||
|
||||
var (svc, _) = BuildService(db);
|
||||
|
||||
var refused = await svc.MergeAsync(task.Id, "main", false, "msg", leaveConflictsInTree: true, CancellationToken.None);
|
||||
Assert.Equal(TaskMergeService.StatusDirtyTarget, refused.Status);
|
||||
|
||||
var result = await svc.MergeAsync(
|
||||
task.Id, "main", false, "msg", leaveConflictsInTree: true, CancellationToken.None, autostash: true);
|
||||
Assert.Equal(TaskMergeService.StatusConflict, result.Status);
|
||||
Assert.Contains("README.md", result.ConflictFiles);
|
||||
Assert.True(await new GitService().IsMidMergeAsync(repo.RepoDir));
|
||||
|
||||
await svc.AbortMergeAsync(task.Id, CancellationToken.None);
|
||||
|
||||
Assert.False(await new GitService().IsMidMergeAsync(repo.RepoDir));
|
||||
Assert.Equal("dirty\n", File.ReadAllText(Path.Combine(repo.RepoDir, "other.txt")).Replace("\r\n", "\n"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteResolutionAsync_ThenContinue_CompletesMerge()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user