use super::*;
use crate::test_support::{fixture, git, git_context, git_text, remove, repository};
use std::ffi::OsStr;
use std::path::PathBuf;
fn rev(repo: &Path, spec: &str) -> String {
return git_text(repo, &["rev-parse", spec]).trim().to_string();
}
fn lines(second: &str, eleventh: &str) -> String {
return (1..=12)
.map(|number| { return match number {
2 => format!("{second}\n"),
11 => format!("{eleventh}\n"),
_ => format!("line {number}\n"),
}; })
.collect();
}
fn scenario(name: &str, prepared_second: &str, prepared_eleventh: &str) -> (PathBuf, String, String, String) {
return scenario_for_file(name, OsStr::new("c.txt"), prepared_second, prepared_eleventh);
}
fn scenario_for_file(name: &str, file: &OsStr, prepared_second: &str, prepared_eleventh: &str) -> (PathBuf, String, String, String) {
let root: PathBuf = fixture(name);
let repo: PathBuf = repository(root.as_path(), "repo");
std::fs::write(repo.join(file), lines("line 2", "line 11")).expect("base");
git(repo.as_path(), &["add", "--all"]);
git(repo.as_path(), &["commit", "--quiet", "--message=base"]);
let base: String = rev(repo.as_path(), "HEAD");
git(repo.as_path(), &["checkout", "--quiet", "-b", "prepared"]);
std::fs::write(repo.join(file), lines(prepared_second, prepared_eleventh)).expect("prepared");
git(repo.as_path(), &["commit", "--quiet", "--all", "--message=prepared"]);
let prepared: String = rev(repo.as_path(), "HEAD");
git(repo.as_path(), &["checkout", "--quiet", "main"]);
std::fs::write(repo.join(file), lines("LANDED", "line 11")).expect("landed");
git(repo.as_path(), &["commit", "--quiet", "--all", "--message=landed"]);
let current: String = rev(repo.as_path(), "HEAD");
return (root, base, current, prepared);
}
fn conflict_paths(merged: &ReplayMerge) -> Vec<Vec<u8>> {
let ReplayMerge::Conflict { paths, .. } = merged else { panic!("expected a conflict") };
return paths.clone();
}
#[test]
fn non_overlapping_hunks_merge_into_one_tree() {
let (root, base, current, prepared) = scenario("replay-merge-clean", "line 2", "OWN");
let repo: PathBuf = root.join("repo");
let merged: ReplayMerge = merge_replay_tree(&git_context(), repo.join(".git").as_path(), repo.as_path(), &base, ¤t, &prepared)
.expect("merge");
let ReplayMerge::Merged { tree_oid } = merged else {
panic!("expected a clean merge");
};
let spec: String = format!("{tree_oid}:c.txt");
let text: String = git_text(repo.as_path(), &["cat-file", "-p", spec.as_str()]);
assert!(text.contains("LANDED\n") && text.contains("OWN\n"), "{text}");
remove(root.as_path());
}
#[test]
fn overlapping_hunks_conflict_and_name_their_paths() {
let (root, base, current, prepared) = scenario("replay-merge-conflict", "PREPARED", "line 11");
let repo: PathBuf = root.join("repo");
let merged: ReplayMerge = merge_replay_tree(&git_context(), repo.join(".git").as_path(), repo.as_path(), &base, ¤t, &prepared)
.expect("merge");
assert_eq!(conflict_paths(&merged), vec![b"c.txt".to_vec()]);
remove(root.as_path());
}
#[test]
fn a_conflict_over_a_name_that_is_not_utf8_reports_its_exact_bytes() {
use std::os::unix::ffi::OsStringExt;
let file: std::ffi::OsString = std::ffi::OsString::from_vec(b"caf\xe9.txt".to_vec());
let (root, base, current, prepared) = scenario_for_file("replay-merge-conflict-odd", file.as_os_str(), "PREPARED", "line 11");
let repo: PathBuf = root.join("repo");
let merged: ReplayMerge = merge_replay_tree(&git_context(), repo.join(".git").as_path(), repo.as_path(), &base, ¤t, &prepared)
.expect("merge");
assert_eq!(conflict_paths(&merged), vec![b"caf\xe9.txt".to_vec()]);
remove(root.as_path());
}
#[test]
fn conflicting_names_are_sorted_by_their_bytes() {
use std::os::unix::ffi::OsStringExt;
let root: PathBuf = fixture("replay-merge-conflict-order");
let repo: PathBuf = repository(root.as_path(), "repo");
let names: [&[u8]; 3] = [b"z.txt", b"caf\xe9.txt", b"a.txt"];
for name in names {
std::fs::write(repo.join(std::ffi::OsString::from_vec(name.to_vec())), lines("line 2", "line 11")).expect("base");
}
git(repo.as_path(), &["add", "--all"]);
git(repo.as_path(), &["commit", "--quiet", "--message=base"]);
let base: String = rev(repo.as_path(), "HEAD");
git(repo.as_path(), &["checkout", "--quiet", "-b", "prepared"]);
for name in names {
std::fs::write(repo.join(std::ffi::OsString::from_vec(name.to_vec())), lines("PREPARED", "line 11")).expect("prepared");
}
git(repo.as_path(), &["commit", "--quiet", "--all", "--message=prepared"]);
let prepared: String = rev(repo.as_path(), "HEAD");
git(repo.as_path(), &["checkout", "--quiet", "main"]);
for name in names {
std::fs::write(repo.join(std::ffi::OsString::from_vec(name.to_vec())), lines("LANDED", "line 11")).expect("landed");
}
git(repo.as_path(), &["commit", "--quiet", "--all", "--message=landed"]);
let current: String = rev(repo.as_path(), "HEAD");
let merged: ReplayMerge = merge_replay_tree(&git_context(), repo.join(".git").as_path(), repo.as_path(), &base, ¤t, &prepared)
.expect("merge");
assert_eq!(conflict_paths(&merged), vec![b"a.txt".to_vec(), b"caf\xe9.txt".to_vec(), b"z.txt".to_vec()]);
remove(root.as_path());
}