feat(merge): Stash-Option fuer uncommittete Aenderungen im Ziel-Tree
Changelog / changelog (push) Successful in 2s
Release / release (push) Successful in 43s

Ein dirty Ziel-Arbeitsverzeichnis gibt jetzt den eigenen Status
dirty_target zurueck statt blocked. Die UI bietet daraufhin einen
Retry an, der per git merge --autostash stasht, merged und die
Aenderungen danach wiederherstellt - opt-in pro Merge:

- Approve & Merge: Bestaetigungsdialog
- Merge-Modal: Button "Aenderungen stashen und mergen"
- MCP (merge_task/review_task) wirft weiterhin, kein autostash dort
This commit is contained in:
mika kuns
2026-09-11 11:43:28 +02:00
parent 8e2d37e63e
commit 251b70ce83
20 changed files with 164 additions and 43 deletions
@@ -392,10 +392,42 @@ public class TaskMergeServiceTests : IDisposable
var (svc, _) = BuildService(db);
var result = await svc.MergeAsync(task.Id, "main", false, "Merge", CancellationToken.None);
Assert.Equal("blocked", result.Status);
Assert.Equal(TaskMergeService.StatusDirtyTarget, result.Status);
Assert.Contains("uncommitted", result.ErrorMessage ?? "");
}
[Fact]
public async Task MergeAsync_DirtyWorkingTree_WithAutostash_MergesAndRestoresTheChanges()
{
if (!GitRepoFixture.IsGitAvailable()) return;
var repo = NewRepo();
var db = NewDb();
var (list, task) = await SeedListAndTask(db, workingDir: repo.RepoDir, status: TaskStatus.Done);
var wtMgr = BuildWorktreeManager(db);
var wtCtx = await wtMgr.CreateAsync(task, list, CancellationToken.None);
_wtCleanups.Add((repo.RepoDir, wtCtx.WorktreePath));
// The branch touches a different file than the dirty one, so the stash restores cleanly.
File.WriteAllText(Path.Combine(wtCtx.WorktreePath, "feature.txt"), "from the branch\n");
GitRepoFixture.RunGit(wtCtx.WorktreePath, "add", "-A");
GitRepoFixture.RunGit(wtCtx.WorktreePath, "commit", "-m", "feat: add feature.txt");
File.WriteAllText(Path.Combine(repo.RepoDir, "README.md"), "dirty\n");
var currentBranch = await new GitService().GetCurrentBranchAsync(repo.RepoDir);
var (svc, _) = BuildService(db);
var result = await svc.MergeAsync(
task.Id, currentBranch, false, "Merge", leaveConflictsInTree: false, CancellationToken.None,
autostash: true);
Assert.True(result.Status == TaskMergeService.StatusMerged, $"{result.Status}: {result.ErrorMessage}");
// The branch landed...
Assert.True(File.Exists(Path.Combine(repo.RepoDir, "feature.txt")));
// ...and the stashed work is back in the working tree, still uncommitted.
Assert.Equal("dirty\n", File.ReadAllText(Path.Combine(repo.RepoDir, "README.md")).Replace("\r\n", "\n"));
}
[Fact]
public async Task MergeAsync_UntrackedFileInTargetWorkingTree_DoesNotBlock()
{