use crate::core::repo;
use crate::core::test_helpers::TestRepo;
#[test]
fn merge_weaves_existing_branch() {
let test_repo = TestRepo::new_with_remote();
test_repo.commit("B1", "b1.txt");
let base_oid = test_repo.find_remote_branch_target("origin/main");
test_repo.create_branch_at("feature-a", &base_oid.to_string());
test_repo.switch_branch("feature-a");
test_repo.commit("A1", "a1.txt");
test_repo.switch_branch("integration");
test_repo
.in_dir(|| super::merge::run(Some("feature-a".to_string()), false))
.unwrap();
assert_eq!(
test_repo.head_commit().parent_count(),
2,
"HEAD should be a merge commit after merging a branch"
);
let info = repo::gather_repo_info(&test_repo.repo, false, 1).unwrap();
let branch_names: Vec<&str> = info.branches.iter().map(|b| b.name.as_str()).collect();
assert!(
branch_names.contains(&"feature-a"),
"feature-a should be in integration branches after merge, got: {:?}",
branch_names
);
}
#[test]
fn merge_already_woven_errors() {
let test_repo = TestRepo::new_with_remote();
test_repo.commit("A1", "a1.txt");
test_repo.commit("A2", "a2.txt");
let a2_oid = test_repo.head_oid();
test_repo
.in_dir(|| crate::branch::new::run(Some("feature-a".to_string()), Some(a2_oid.to_string())))
.unwrap();
let result = test_repo.in_dir(|| super::merge::run(Some("feature-a".to_string()), false));
assert!(result.is_err(), "merging already-woven branch should error");
}
#[test]
fn merge_then_unmerge_round_trip() {
let test_repo = TestRepo::new_with_remote();
let base_oid = test_repo.find_remote_branch_target("origin/main");
test_repo.create_branch_at("feature-rt", &base_oid.to_string());
test_repo.switch_branch("feature-rt");
test_repo.commit("RT1", "rt1.txt");
test_repo.commit("RT2", "rt2.txt");
test_repo.switch_branch("integration");
test_repo
.in_dir(|| super::merge::run(Some("feature-rt".to_string()), false))
.unwrap();
let info = repo::gather_repo_info(&test_repo.repo, false, 1).unwrap();
let woven: Vec<&str> = info.branches.iter().map(|b| b.name.as_str()).collect();
assert!(
woven.contains(&"feature-rt"),
"branch should be woven after merge"
);
test_repo
.in_dir(|| super::unmerge::run(Some("feature-rt".to_string())))
.unwrap();
let info = repo::gather_repo_info(&test_repo.repo, false, 1).unwrap();
let woven: Vec<&str> = info.branches.iter().map(|b| b.name.as_str()).collect();
assert!(
!woven.contains(&"feature-rt"),
"branch should not be woven after unmerge"
);
assert!(test_repo.branch_exists("feature-rt"));
let tip = test_repo.get_branch_target("feature-rt");
let tip_commit = test_repo.find_commit(tip);
assert_eq!(tip_commit.summary().unwrap_or(""), "RT2");
}
#[test]
fn merge_nonexistent_branch_errors() {
let test_repo = TestRepo::new_with_remote();
test_repo.commit("A1", "a1.txt");
let result =
test_repo.in_dir(|| super::merge::run(Some("nonexistent-branch".to_string()), false));
assert!(result.is_err(), "merging nonexistent branch should error");
}
#[test]
fn merge_conflict_continue_weaves_branch() {
let test_repo = TestRepo::new_with_remote();
let base_oid = test_repo.find_remote_branch_target("origin/main");
test_repo.create_branch_at("feature-conflict", &base_oid.to_string());
test_repo.write_file("shared.txt", "integration");
test_repo.stage_files(&["shared.txt"]);
test_repo.commit_staged("Integration change");
test_repo.switch_branch("feature-conflict");
test_repo.write_file("shared.txt", "feature");
test_repo.stage_files(&["shared.txt"]);
test_repo.commit_staged("Feature change");
test_repo.switch_branch("integration");
let pre_merge_head = test_repo.head_oid();
let result =
test_repo.in_dir(|| super::merge::run(Some("feature-conflict".to_string()), false));
assert!(
result.is_ok(),
"merge should return Ok on conflict: {:?}",
result
);
let state_path = test_repo.repo.path().join("loom/state.json");
assert!(
state_path.exists(),
"state file must exist when merge is paused"
);
assert_eq!(
test_repo.head_oid(),
pre_merge_head,
"HEAD must not move on conflict"
);
test_repo.write_file("shared.txt", "resolved");
test_repo.stage_files(&["shared.txt"]);
crate::core::transaction::continue_cmd(&test_repo.workdir(), test_repo.repo.path()).unwrap();
assert!(
!state_path.exists(),
"state file must be deleted after continue"
);
assert_eq!(
test_repo.head_commit().parent_count(),
2,
"HEAD must be a merge commit after continue"
);
let info = repo::gather_repo_info(&test_repo.repo, false, 1).unwrap();
let branch_names: Vec<&str> = info.branches.iter().map(|b| b.name.as_str()).collect();
assert!(
branch_names.contains(&"feature-conflict"),
"branch must be woven after continue, got: {:?}",
branch_names
);
}
#[test]
fn merge_conflict_abort_restores_state() {
let test_repo = TestRepo::new_with_remote();
let base_oid = test_repo.find_remote_branch_target("origin/main");
test_repo.create_branch_at("feature-abort", &base_oid.to_string());
test_repo.write_file("shared.txt", "integration");
test_repo.stage_files(&["shared.txt"]);
test_repo.commit_staged("Integration change");
test_repo.switch_branch("feature-abort");
test_repo.write_file("shared.txt", "feature");
test_repo.stage_files(&["shared.txt"]);
test_repo.commit_staged("Feature change");
test_repo.switch_branch("integration");
let pre_merge_head = test_repo.head_oid();
let result = test_repo.in_dir(|| super::merge::run(Some("feature-abort".to_string()), false));
assert!(
result.is_ok(),
"merge should return Ok on conflict: {:?}",
result
);
let state_path = test_repo.repo.path().join("loom/state.json");
assert!(
state_path.exists(),
"state file must exist when merge is paused"
);
crate::core::transaction::abort_cmd(&test_repo.workdir(), test_repo.repo.path()).unwrap();
assert!(
!state_path.exists(),
"state file must be deleted after abort"
);
assert_eq!(
test_repo.head_oid(),
pre_merge_head,
"HEAD must be restored after abort"
);
assert_eq!(
test_repo.head_commit().parent_count(),
1,
"HEAD must not be a merge commit after abort"
);
let info = repo::gather_repo_info(&test_repo.repo, false, 1).unwrap();
let branch_names: Vec<&str> = info.branches.iter().map(|b| b.name.as_str()).collect();
assert!(
!branch_names.contains(&"feature-abort"),
"branch must NOT be woven after abort, got: {:?}",
branch_names
);
}