use crate::common;
use common::{OutputAssertions, TestRepo};
use serde_json::Value;
fn branch_needs_restack(status: &Value, branch: &str) -> Option<bool> {
status["branches"].as_array().and_then(|branches| {
branches
.iter()
.find(|b| b["name"].as_str() == Some(branch))
.and_then(|b| b["needs_restack"].as_bool())
})
}
#[test]
fn test_continue_no_rebase_in_progress() {
let repo = TestRepo::new();
repo.create_stack(&["feature-1"]);
let output = repo.run_stax(&["continue"]);
let stdout = TestRepo::stdout(&output);
let stderr = TestRepo::stderr(&output);
let combined = format!("{}{}", stdout, stderr);
assert!(
combined.contains("No rebase")
|| combined.contains("no rebase")
|| combined.contains("in progress"),
"Expected 'no rebase in progress' message, got: {}",
combined
);
}
#[test]
fn test_continue_alias_cont() {
let repo = TestRepo::new();
repo.create_stack(&["feature-1"]);
let output = repo.run_stax(&["cont"]);
let stdout = TestRepo::stdout(&output);
let stderr = TestRepo::stderr(&output);
let combined = format!("{}{}", stdout, stderr);
assert!(
combined.contains("No rebase")
|| combined.contains("no rebase")
|| combined.contains("in progress"),
"Expected 'no rebase' message, got: {}",
combined
);
}
#[test]
fn test_continue_help() {
let repo = TestRepo::new();
let output = repo.run_stax(&["continue", "--help"]);
output.assert_success();
let stdout = TestRepo::stdout(&output);
assert!(
stdout.contains("Continue") || stdout.contains("continue") || stdout.contains("conflict"),
"Expected continue help text, got: {}",
stdout
);
}
#[test]
fn test_continue_scenario_setup_creates_conflict() {
let repo = TestRepo::new();
repo.run_stax(&["bc", "conflict-branch"]);
let branch_name = repo.current_branch();
repo.create_file("conflict.txt", "feature content\nline 2\nline 3");
repo.commit("Feature changes");
repo.run_stax(&["t"]);
repo.create_file("conflict.txt", "main content\nline 2\nline 3");
repo.commit("Main changes");
repo.run_stax(&["checkout", &branch_name]);
let json = repo.get_status_json();
let branches = json["branches"].as_array().unwrap();
let feature = branches
.iter()
.find(|b| b["name"].as_str().unwrap_or("").contains("conflict-branch"));
assert!(feature.is_some(), "Should find conflict-branch");
if let Some(f) = feature {
assert!(
f["needs_restack"].as_bool().unwrap_or(false),
"Branch should need restack after parent changed"
);
}
}
#[test]
fn test_continue_scenario_no_rebase_without_conflicts() {
let repo = TestRepo::new();
repo.run_stax(&["bc", "feature-1"]);
let branch_name = repo.current_branch();
repo.create_file("feature.txt", "feature content");
repo.commit("Feature changes");
repo.run_stax(&["t"]);
repo.create_file("main.txt", "main content");
repo.commit("Main changes");
repo.run_stax(&["checkout", &branch_name]);
let output = repo.run_stax(&["restack", "--quiet"]);
output.assert_success();
let output = repo.run_stax(&["continue"]);
let stdout = TestRepo::stdout(&output);
let stderr = TestRepo::stderr(&output);
let combined = format!("{}{}", stdout, stderr);
assert!(
combined.contains("No rebase")
|| combined.contains("no rebase")
|| combined.contains("in progress"),
"Expected 'no rebase' after successful restack, got: {}",
combined
);
}
#[test]
fn test_continue_after_restack_creates_conflict_marker() {
let repo = TestRepo::new();
let _branch_name = repo.create_conflict_scenario();
let output = repo.run_stax(&["restack", "--quiet"]);
let stdout = TestRepo::stdout(&output);
let stderr = TestRepo::stderr(&output);
let combined = format!("{}{}", stdout, stderr);
if combined.contains("conflict") || combined.contains("Conflict") {
assert!(
repo.has_rebase_in_progress(),
"Should have rebase in progress after conflict"
);
repo.abort_rebase();
}
}
#[test]
fn test_continue_resumes_remaining_restack_after_conflict() {
let repo = TestRepo::new();
repo.run_stax(&["bc", "resume-parent"]);
let parent = repo.current_branch();
repo.create_file("parent.txt", "parent content\n");
repo.commit("Parent commit");
repo.run_stax(&["bc", "resume-child"]);
let child = repo.current_branch();
repo.create_file("conflict.txt", "child content\n");
repo.commit("Child commit");
repo.run_stax(&["bc", "resume-grandchild"]);
let grandchild = repo.current_branch();
repo.create_file("grandchild.txt", "grandchild content\n");
repo.commit("Grandchild commit");
repo.run_stax(&["t"]);
repo.create_file("conflict.txt", "main content\n");
repo.commit("Main conflict commit");
repo.run_stax(&["checkout", &grandchild]);
let output = repo.run_stax(&["restack", "--quiet"]);
assert!(
!output.status.success(),
"Restack should exit non-zero on conflict\nstdout: {}\nstderr: {}",
TestRepo::stdout(&output),
TestRepo::stderr(&output)
);
assert!(
repo.has_rebase_in_progress(),
"Expected a rebase in progress after the conflict"
);
repo.resolve_conflicts_ours();
let output = repo.run_stax(&["continue"]);
output.assert_success();
let status = repo.get_status_json();
assert_eq!(
branch_needs_restack(&status, &grandchild),
Some(false),
"Grandchild should be fully restacked after continue"
);
assert_eq!(
branch_needs_restack(&status, &child),
Some(false),
"Child should remain restacked after continue"
);
assert_eq!(
branch_needs_restack(&status, &parent),
Some(false),
"Parent should remain restacked after continue"
);
}
#[test]
fn test_sync_continue_flag() {
let repo = TestRepo::new();
repo.create_stack(&["feature-1"]);
let output = repo.run_stax(&["sync", "--continue", "--force"]);
let stdout = TestRepo::stdout(&output);
let stderr = TestRepo::stderr(&output);
let combined = format!("{}{}", stdout, stderr);
assert!(
combined.contains("No rebase")
|| combined.contains("no rebase")
|| combined.contains("remote")
|| combined.contains("origin")
|| output.status.success(),
"Expected continue to be processed or remote error, got: {}",
combined
);
}
#[test]
fn test_restack_continue_flag() {
let repo = TestRepo::new();
repo.create_stack(&["feature-1"]);
let output = repo.run_stax(&["restack", "--continue"]);
let stdout = TestRepo::stdout(&output);
let stderr = TestRepo::stderr(&output);
let combined = format!("{}{}", stdout, stderr);
assert!(
combined.contains("No rebase")
|| combined.contains("no rebase")
|| combined.contains("up to date")
|| output.status.success(),
"Expected continue handling, got: {}",
combined
);
}
#[test]
fn test_continue_on_trunk() {
let repo = TestRepo::new();
repo.create_stack(&["feature-1"]);
repo.run_stax(&["t"]);
assert_eq!(repo.current_branch(), "main");
let output = repo.run_stax(&["continue"]);
let stdout = TestRepo::stdout(&output);
let stderr = TestRepo::stderr(&output);
let combined = format!("{}{}", stdout, stderr);
assert!(
combined.contains("No rebase")
|| combined.contains("no rebase")
|| combined.contains("in progress"),
"Expected 'no rebase' on trunk, got: {}",
combined
);
}
#[test]
fn test_continue_on_untracked_branch() {
let repo = TestRepo::new();
repo.git(&["checkout", "-b", "untracked"]);
repo.create_file("test.txt", "content");
repo.commit("Untracked commit");
let output = repo.run_stax(&["continue"]);
let stdout = TestRepo::stdout(&output);
let stderr = TestRepo::stderr(&output);
let combined = format!("{}{}", stdout, stderr);
assert!(
combined.contains("No rebase")
|| combined.contains("no rebase")
|| combined.contains("in progress")
|| !output.status.success(),
"Expected graceful handling on untracked branch, got: {}",
combined
);
}
#[test]
fn test_continue_multiple_times_no_rebase() {
let repo = TestRepo::new();
repo.create_stack(&["feature-1"]);
for _ in 0..3 {
let output = repo.run_stax(&["continue"]);
let stdout = TestRepo::stdout(&output);
assert!(
stdout.contains("No rebase") || stdout.contains("no rebase"),
"Expected 'no rebase' message"
);
}
}