huskies: merge 777
This commit is contained in:
@@ -145,27 +145,26 @@ pub(crate) fn run_squash_merge(
|
|||||||
all_output.push('\n');
|
all_output.push('\n');
|
||||||
|
|
||||||
if !commit.status.success() {
|
if !commit.status.success() {
|
||||||
// Bug 226: "nothing to commit" means the feature branch has no changes
|
|
||||||
// beyond what's already on master. This must NOT be treated as success
|
|
||||||
// — it means the code was never actually merged.
|
|
||||||
if commit_stderr.contains("nothing to commit")
|
if commit_stderr.contains("nothing to commit")
|
||||||
|| commit_stdout.contains("nothing to commit")
|
|| commit_stdout.contains("nothing to commit")
|
||||||
{
|
{
|
||||||
all_output.push_str(
|
// Bug 777: "nothing to commit" after a clean squash means the feature
|
||||||
"=== Nothing to commit — feature branch has no changes beyond master ===\n",
|
// branch's content is already on `base_branch` (idempotent retry after
|
||||||
);
|
// a previous successful merge). Return success so the caller advances
|
||||||
|
// the story to `5_done` instead of overwriting that state with
|
||||||
|
// `merge_failure`. The pre-flight `ahead == 0` check above already
|
||||||
|
// catches genuinely empty feature branches, so reaching this point
|
||||||
|
// implies "ahead > 0 but already merged".
|
||||||
|
all_output
|
||||||
|
.push_str("=== Nothing to commit — feature branch already merged into base ===\n");
|
||||||
cleanup_merge_workspace(project_root, &merge_wt_path, &merge_branch);
|
cleanup_merge_workspace(project_root, &merge_wt_path, &merge_branch);
|
||||||
return Ok(SquashMergeResult {
|
return Ok(SquashMergeResult {
|
||||||
success: false,
|
success: true,
|
||||||
had_conflicts,
|
had_conflicts: false,
|
||||||
conflicts_resolved,
|
conflicts_resolved: false,
|
||||||
conflict_details: Some(
|
conflict_details: None,
|
||||||
"Squash-merge resulted in an empty diff — the feature branch has no \
|
|
||||||
code changes to merge into master."
|
|
||||||
.to_string(),
|
|
||||||
),
|
|
||||||
output: all_output,
|
output: all_output,
|
||||||
gates_passed: false,
|
gates_passed: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
cleanup_merge_workspace(project_root, &merge_wt_path, &merge_branch);
|
cleanup_merge_workspace(project_root, &merge_wt_path, &merge_branch);
|
||||||
|
|||||||
@@ -361,3 +361,79 @@ async fn squash_merge_empty_diff_fails() {
|
|||||||
"merge workspace should be cleaned up"
|
"merge workspace should be cleaned up"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bug 777: a second `run_squash_merge` call after a successful first one must
|
||||||
|
/// return `success: true` (idempotent) so the caller advances the story to
|
||||||
|
/// `5_done` rather than overwriting that state with `merge_failure`. The
|
||||||
|
/// pre-flight `ahead == 0` check still catches truly empty feature branches.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn idempotent_retry_after_successful_merge_returns_success() {
|
||||||
|
use std::fs;
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
let tmp = tempdir().unwrap();
|
||||||
|
let repo = tmp.path();
|
||||||
|
init_git_repo(repo);
|
||||||
|
|
||||||
|
// Master has an initial file.
|
||||||
|
fs::write(repo.join("base.txt"), "base\n").unwrap();
|
||||||
|
Command::new("git")
|
||||||
|
.args(["add", "."])
|
||||||
|
.current_dir(repo)
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
Command::new("git")
|
||||||
|
.args(["commit", "-m", "base"])
|
||||||
|
.current_dir(repo)
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Feature branch adds a new file.
|
||||||
|
Command::new("git")
|
||||||
|
.args(["checkout", "-b", "feature/story-777_idem"])
|
||||||
|
.current_dir(repo)
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
fs::write(repo.join("feat.txt"), "feature\n").unwrap();
|
||||||
|
Command::new("git")
|
||||||
|
.args(["add", "."])
|
||||||
|
.current_dir(repo)
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
Command::new("git")
|
||||||
|
.args(["commit", "-m", "add feat"])
|
||||||
|
.current_dir(repo)
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
Command::new("git")
|
||||||
|
.args(["checkout", "master"])
|
||||||
|
.current_dir(repo)
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// First merge: should succeed and land feature's content on master.
|
||||||
|
let r1 = run_squash_merge(repo, "feature/story-777_idem", "777_idem")
|
||||||
|
.expect("first merge produces Ok");
|
||||||
|
// The merge may fail gates in test env (no script/test); only require that
|
||||||
|
// the squash applied SOMETHING (cargo gates env-dependent).
|
||||||
|
if r1.success {
|
||||||
|
// Second merge of the SAME branch: must report success (idempotent),
|
||||||
|
// not merge_failure. Feature branch's content is already on master so
|
||||||
|
// the squash produces "nothing to commit" — bug 777 makes this success.
|
||||||
|
let r2 = run_squash_merge(repo, "feature/story-777_idem", "777_idem")
|
||||||
|
.expect("second merge produces Ok");
|
||||||
|
assert!(
|
||||||
|
r2.success,
|
||||||
|
"idempotent retry must return success: {}",
|
||||||
|
r2.output
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!r2.had_conflicts,
|
||||||
|
"idempotent retry should report no conflicts"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
r2.conflict_details.is_none(),
|
||||||
|
"no conflict_details on idempotent retry"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user