fix(426): verify cherry-pick landed on master before marking story done
After the cherry-pick step in run_squash_merge, verify: 1. project_root is on the base branch (not a merge-queue branch) 2. HEAD commit has actual code changes (not an empty/story-only diff) If either check fails, return success=false so the story stays in merge stage for retry instead of being phantom-advanced to done. Also rename move_story_to_archived → move_story_to_done. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -383,6 +383,71 @@ pub(crate) fn run_squash_merge(
|
||||
});
|
||||
}
|
||||
|
||||
// ── Verify code landed on the correct branch ──────────────────
|
||||
// Guard against the cherry-pick silently landing on the wrong branch
|
||||
// (e.g. a merge-queue branch from a concurrent merge). If the current
|
||||
// branch is not the base branch, or the HEAD commit has no code diff,
|
||||
// treat the merge as failed so the story stays in the merge stage.
|
||||
let current_branch = Command::new("git")
|
||||
.args(["rev-parse", "--abbrev-ref", "HEAD"])
|
||||
.current_dir(project_root)
|
||||
.output()
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let base_branch = crate::config::ProjectConfig::load(project_root)
|
||||
.ok()
|
||||
.and_then(|c| c.base_branch.clone())
|
||||
.unwrap_or_else(|| "master".to_string());
|
||||
|
||||
if current_branch != base_branch {
|
||||
all_output.push_str(&format!(
|
||||
"=== VERIFICATION FAILED: expected branch '{base_branch}' but HEAD is on \
|
||||
'{current_branch}'. Cherry-pick landed on wrong branch. ===\n"
|
||||
));
|
||||
cleanup_merge_workspace(project_root, &merge_wt_path, &merge_branch);
|
||||
return Ok(SquashMergeResult {
|
||||
success: false,
|
||||
had_conflicts,
|
||||
conflicts_resolved,
|
||||
conflict_details: Some(format!(
|
||||
"Cherry-pick landed on '{current_branch}' instead of '{base_branch}'"
|
||||
)),
|
||||
output: all_output,
|
||||
gates_passed: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Verify HEAD commit has actual code changes (not an empty cherry-pick).
|
||||
// Exclude .storkit/ so that story-file-only commits don't pass this check.
|
||||
let diff_stat = Command::new("git")
|
||||
.args(["diff", "--stat", "HEAD~1..HEAD", "--", ".", ":(exclude).storkit"])
|
||||
.current_dir(project_root)
|
||||
.output()
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
if diff_stat.is_empty() {
|
||||
all_output.push_str(
|
||||
"=== VERIFICATION FAILED: cherry-pick produced no code changes on master. ===\n",
|
||||
);
|
||||
cleanup_merge_workspace(project_root, &merge_wt_path, &merge_branch);
|
||||
return Ok(SquashMergeResult {
|
||||
success: false,
|
||||
had_conflicts,
|
||||
conflicts_resolved,
|
||||
conflict_details: Some(
|
||||
"Cherry-pick commit contains no code changes (empty diff)".to_string(),
|
||||
),
|
||||
output: all_output,
|
||||
gates_passed: true,
|
||||
});
|
||||
}
|
||||
|
||||
all_output.push_str(&format!(
|
||||
"=== Verified: cherry-pick landed on '{base_branch}' with code changes ===\n"
|
||||
));
|
||||
|
||||
// ── Clean up ──────────────────────────────────────────────────
|
||||
cleanup_merge_workspace(project_root, &merge_wt_path, &merge_branch);
|
||||
all_output.push_str("=== Merge-queue cleanup complete ===\n");
|
||||
|
||||
Reference in New Issue
Block a user