fix(worker): make re-approve after revert_merge do a real merge, not a silent no-op
ApproveAndMergeAsync treated any non-Active worktree (including Kept, the
state RevertMergeAsync leaves behind) the same as "never had a worktree",
so re-approving a reverted task ran only the verify gate and marked the
task Done without merging anything back.
Now a Kept worktree is distinguished: if its branch is gone (the common
case, since approve normally deletes it), approve returns Blocked instead
of a fake "merged". If the branch survives, its tip is already an ancestor
of the target (from the original merge), so a plain `git merge` would
silently no-op ("Already up to date."); cherry-picking the original merge
commit's own diff (-m 1) is what actually restores the content, recorded
as a fresh MergeCommit.
This commit is contained in:
@@ -356,6 +356,38 @@ public sealed class GitService
|
||||
throw new InvalidOperationException($"git revert --abort failed (exit {exitCode}): {stderr}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-applies a merge commit's own diff (against its first parent, `-m 1`) as a new commit on
|
||||
/// top of HEAD. Needed to redo a merge that was previously reverted: once a branch tip is an
|
||||
/// ancestor of HEAD, `git merge` on that branch again is a silent "Already up to date." no-op
|
||||
/// (ancestry, not content, is what merge checks) -- cherry-picking the original merge commit's
|
||||
/// diff is what actually restores the content.
|
||||
/// </summary>
|
||||
public async Task<(int ExitCode, string Stderr)> CherryPickMergeCommitAsync(
|
||||
string repoDir, string mergeCommitSha, CancellationToken ct = default)
|
||||
{
|
||||
var (exitCode, _, stderr) = await RunGitAsync(repoDir,
|
||||
["-c", "merge.conflictStyle=diff3", "cherry-pick", "-m", "1", "--no-edit", mergeCommitSha], ct);
|
||||
return (exitCode, stderr);
|
||||
}
|
||||
|
||||
public async Task<bool> IsMidCherryPickAsync(string repoDir, CancellationToken ct = default)
|
||||
{
|
||||
var (exitCode, stdout, _) = await RunGitAsync(repoDir, ["rev-parse", "--git-dir"], ct);
|
||||
if (exitCode != 0) return false;
|
||||
var gitDir = stdout.Trim();
|
||||
if (!Path.IsPathRooted(gitDir))
|
||||
gitDir = Path.Combine(repoDir, gitDir);
|
||||
return File.Exists(Path.Combine(gitDir, "CHERRY_PICK_HEAD"));
|
||||
}
|
||||
|
||||
public async Task CherryPickAbortAsync(string repoDir, CancellationToken ct = default)
|
||||
{
|
||||
var (exitCode, _, stderr) = await RunGitAsync(repoDir, ["cherry-pick", "--abort"], ct);
|
||||
if (exitCode != 0)
|
||||
throw new InvalidOperationException($"git cherry-pick --abort failed (exit {exitCode}): {stderr}");
|
||||
}
|
||||
|
||||
public async Task<List<string>> ListConflictedFilesAsync(string repoDir, CancellationToken ct = default)
|
||||
{
|
||||
var (exitCode, stdout, stderr) = await RunGitAsync(repoDir,
|
||||
|
||||
Reference in New Issue
Block a user