feat(merge): fold parent branch into combined-diff for improvement parents

This commit is contained in:
mika kuns
2026-06-04 16:53:42 +02:00
parent 176b9855bf
commit 469e68bbc8

View File

@@ -43,17 +43,24 @@ public sealed class PlanningAggregator
string planningTaskId, CancellationToken ct) string planningTaskId, CancellationToken ct)
{ {
using var ctx = _dbFactory.CreateDbContext(); using var ctx = _dbFactory.CreateDbContext();
var children = await ctx.Tasks var parent = await ctx.Tasks
.Include(t => t.Worktree) .Include(t => t.Worktree)
.Where(t => t.ParentTaskId == planningTaskId) .Include(t => t.Children).ThenInclude(c => c.Worktree)
.OrderBy(t => t.SortOrder) .SingleOrDefaultAsync(t => t.Id == planningTaskId, ct);
.ToListAsync(ct); if (parent is null) return new List<SubtaskDiff>();
var nodes = new List<TaskEntity>();
// An improvement parent carries its own code branch — fold it in first so the
// combined diff matches what the tree-merge will produce.
if (parent.PlanningPhase == PlanningPhase.None && parent.Worktree is { State: WorktreeState.Active })
nodes.Add(parent);
nodes.AddRange(parent.Children.OrderBy(c => c.SortOrder));
var result = new List<SubtaskDiff>(); var result = new List<SubtaskDiff>();
foreach (var child in children) foreach (var node in nodes)
{ {
if (child.Worktree is null) continue; if (node.Worktree is null) continue;
var wt = child.Worktree; var wt = node.Worktree;
var head = wt.HeadCommit ?? await _git.RevParseHeadAsync(wt.Path, ct); var head = wt.HeadCommit ?? await _git.RevParseHeadAsync(wt.Path, ct);
string unified; string unified;
try try
@@ -62,11 +69,11 @@ public sealed class PlanningAggregator
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogWarning(ex, "diff failed for subtask {Id}", child.Id); _logger.LogWarning(ex, "diff failed for node {Id}", node.Id);
unified = ""; unified = "";
} }
result.Add(new SubtaskDiff( result.Add(new SubtaskDiff(
child.Id, child.Title, wt.BranchName, wt.BaseCommit, head, wt.DiffStat, unified)); node.Id, node.Title, wt.BranchName, wt.BaseCommit, head, wt.DiffStat, unified));
} }
return result; return result;
} }
@@ -87,12 +94,18 @@ public sealed class PlanningAggregator
await GitRawAsync(repoDir, ct, "checkout", "-b", integrationBranch); await GitRawAsync(repoDir, ct, "checkout", "-b", integrationBranch);
foreach (var child in childSubtasks) var nodes = new List<TaskEntity>();
// Fold the improvement parent's own branch in first (planning parents have none).
if (planning.PlanningPhase == PlanningPhase.None && planning.Worktree is { State: WorktreeState.Active })
nodes.Add(planning);
nodes.AddRange(childSubtasks);
foreach (var node in nodes)
{ {
if (child.Worktree is null) continue; if (node.Worktree is null) continue;
var (code, _) = await _git.MergeNoFfAsync( var (code, _) = await _git.MergeNoFfAsync(
repoDir, child.Worktree.BranchName, repoDir, node.Worktree.BranchName,
$"Integrate subtask: {child.Title}", ct); $"Integrate: {node.Title}", ct);
if (code != 0) if (code != 0)
{ {
List<string> files; List<string> files;
@@ -104,7 +117,7 @@ public sealed class PlanningAggregator
try { await _git.BranchDeleteAsync(repoDir, integrationBranch, force: true, ct); } catch { } try { await _git.BranchDeleteAsync(repoDir, integrationBranch, force: true, ct); } catch { }
return new CombinedDiffResult.Failed( return new CombinedDiffResult.Failed(
new CombinedDiffFailure(child.Id, files)); new CombinedDiffFailure(node.Id, files));
} }
} }
@@ -135,6 +148,7 @@ public sealed class PlanningAggregator
using var ctx = _dbFactory.CreateDbContext(); using var ctx = _dbFactory.CreateDbContext();
var planning = await ctx.Tasks var planning = await ctx.Tasks
.Include(t => t.List) .Include(t => t.List)
.Include(t => t.Worktree)
.Include(t => t.Children).ThenInclude(c => c.Worktree) .Include(t => t.Children).ThenInclude(c => c.Worktree)
.SingleOrDefaultAsync(t => t.Id == planningTaskId, ct) .SingleOrDefaultAsync(t => t.Id == planningTaskId, ct)
?? throw new KeyNotFoundException($"Planning task '{planningTaskId}' not found."); ?? throw new KeyNotFoundException($"Planning task '{planningTaskId}' not found.");