211 lines
6.4 KiB
Rust
211 lines
6.4 KiB
Rust
use gitcomet_core::merge::{MergeOptions, merge_file};
|
|
use gitcomet_core::merge_extraction::{
|
|
MergeExtractionOptions, discover_merge_commits, extract_merge_cases_from_repo,
|
|
};
|
|
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::Command;
|
|
|
|
fn resource_root() -> PathBuf {
|
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
.join("tests")
|
|
.join("libgit2")
|
|
.join("resources")
|
|
}
|
|
|
|
fn copy_dir_recursive(src: &Path, dst: &Path) -> std::io::Result<()> {
|
|
fs::create_dir_all(dst)?;
|
|
|
|
for entry in fs::read_dir(src)? {
|
|
let entry = entry?;
|
|
let src_path = entry.path();
|
|
let dst_path = dst.join(entry.file_name());
|
|
let file_type = entry.file_type()?;
|
|
|
|
if file_type.is_dir() {
|
|
copy_dir_recursive(&src_path, &dst_path)?;
|
|
} else {
|
|
fs::copy(&src_path, &dst_path)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
struct RehydratedRepo {
|
|
_tempdir: tempfile::TempDir,
|
|
worktree_path: PathBuf,
|
|
}
|
|
|
|
fn rehydrate_worktree_repo(name: &str) -> RehydratedRepo {
|
|
let src = resource_root().join(name);
|
|
assert!(src.is_dir(), "missing resource repo: {}", src.display());
|
|
assert!(
|
|
src.join(".gitted").is_dir(),
|
|
"expected .gitted dir in {}",
|
|
src.display()
|
|
);
|
|
|
|
let tempdir = tempfile::tempdir().expect("create temp dir");
|
|
let worktree_path = tempdir.path().join(name);
|
|
copy_dir_recursive(&src, &worktree_path).expect("copy fixture repo");
|
|
|
|
let gitted = worktree_path.join(".gitted");
|
|
let git_dir = worktree_path.join(".git");
|
|
fs::rename(&gitted, &git_dir).expect("rehydrate .gitted -> .git");
|
|
|
|
RehydratedRepo {
|
|
_tempdir: tempdir,
|
|
worktree_path,
|
|
}
|
|
}
|
|
|
|
fn run_git_in_worktree(repo: &Path, args: &[&str]) -> String {
|
|
let output = Command::new("git")
|
|
.args(args)
|
|
.current_dir(repo)
|
|
.output()
|
|
.unwrap_or_else(|e| panic!("failed to run git {:?}: {e}", args));
|
|
assert!(
|
|
output.status.success(),
|
|
"git {:?} failed: {}",
|
|
args,
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
String::from_utf8_lossy(&output.stdout).into_owned()
|
|
}
|
|
|
|
fn run_git_with_git_dir(git_dir: &Path, args: &[&str]) -> String {
|
|
let output = Command::new("git")
|
|
.arg(format!("--git-dir={}", git_dir.display()))
|
|
.args(args)
|
|
.output()
|
|
.unwrap_or_else(|e| panic!("failed to run git {:?}: {e}", args));
|
|
assert!(
|
|
output.status.success(),
|
|
"git {:?} failed: {}",
|
|
args,
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
String::from_utf8_lossy(&output.stdout).into_owned()
|
|
}
|
|
|
|
#[test]
|
|
fn libgit2_merge_resource_layout_matches_upstream_style() {
|
|
let root = resource_root();
|
|
|
|
for name in [
|
|
"merge-recursive",
|
|
"merge-resolve",
|
|
"merge-whitespace",
|
|
"mergedrepo",
|
|
] {
|
|
let repo = root.join(name);
|
|
assert!(repo.is_dir(), "missing {}", repo.display());
|
|
assert!(
|
|
repo.join(".gitted").is_dir(),
|
|
"missing .gitted in {}",
|
|
repo.display()
|
|
);
|
|
assert!(
|
|
repo.join(".gitted").join("HEAD").is_file(),
|
|
"missing .gitted/HEAD in {}",
|
|
repo.display()
|
|
);
|
|
}
|
|
|
|
for name in ["redundant.git", "twowaymerge.git"] {
|
|
let repo = root.join(name);
|
|
assert!(repo.is_dir(), "missing {}", repo.display());
|
|
assert!(
|
|
repo.join("HEAD").is_file(),
|
|
"missing HEAD in {}",
|
|
repo.display()
|
|
);
|
|
let is_bare = run_git_with_git_dir(&repo, &["rev-parse", "--is-bare-repository"]);
|
|
assert_eq!(is_bare.trim(), "true", "{} should be bare", repo.display());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn merge_recursive_rehydration_extracts_real_text_cases() {
|
|
let repo = rehydrate_worktree_repo("merge-recursive");
|
|
let git_dir = run_git_in_worktree(&repo.worktree_path, &["rev-parse", "--git-dir"]);
|
|
assert_eq!(git_dir.trim(), ".git");
|
|
|
|
let merges = discover_merge_commits(&repo.worktree_path, 20).expect("discover merges");
|
|
assert!(
|
|
!merges.is_empty(),
|
|
"expected merge commits in merge-recursive"
|
|
);
|
|
|
|
let cases = extract_merge_cases_from_repo(
|
|
&repo.worktree_path,
|
|
MergeExtractionOptions {
|
|
max_merges: 20,
|
|
max_files_per_merge: 20,
|
|
},
|
|
)
|
|
.expect("extract merge cases");
|
|
|
|
assert!(!cases.is_empty(), "expected extracted merge cases");
|
|
assert!(
|
|
cases.iter().all(|case| {
|
|
!case.base.contains("anonymous blob ")
|
|
&& !case.contrib1.contains("anonymous blob ")
|
|
&& !case.contrib2.contains("anonymous blob ")
|
|
}),
|
|
"merge-recursive should yield real text payloads"
|
|
);
|
|
assert!(
|
|
cases.iter().any(|case| {
|
|
case.base.contains("VEAL SOUP.")
|
|
|| case.contrib1.contains("VEAL SOUP.")
|
|
|| case.contrib2.contains("VEAL SOUP.")
|
|
}),
|
|
"expected known merge-recursive corpus text in extracted cases"
|
|
);
|
|
|
|
for case in cases.iter().take(12) {
|
|
let result = merge_file(
|
|
&case.base,
|
|
&case.contrib1,
|
|
&case.contrib2,
|
|
&MergeOptions::default(),
|
|
);
|
|
assert!(
|
|
!result.output.is_empty(),
|
|
"merge output should not be empty for case {}:{}",
|
|
case.merge_commit,
|
|
case.file_path
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn rehydrated_worktree_fixtures_match_libgit2_payloads() {
|
|
let merge_resolve = rehydrate_worktree_repo("merge-resolve");
|
|
assert_eq!(
|
|
run_git_in_worktree(&merge_resolve.worktree_path, &["rev-parse", "--git-dir"]).trim(),
|
|
".git"
|
|
);
|
|
let conflicting =
|
|
fs::read_to_string(merge_resolve.worktree_path.join("conflicting.txt")).expect("read");
|
|
assert!(conflicting.contains("changed in master and branch"));
|
|
|
|
let merge_whitespace = rehydrate_worktree_repo("merge-whitespace");
|
|
let whitespace_text =
|
|
fs::read_to_string(merge_whitespace.worktree_path.join("test.txt")).expect("read");
|
|
assert!(
|
|
whitespace_text
|
|
.lines()
|
|
.next()
|
|
.is_some_and(|line| line == "0")
|
|
);
|
|
|
|
let mergedrepo = rehydrate_worktree_repo("mergedrepo");
|
|
let conflicts_one =
|
|
fs::read_to_string(mergedrepo.worktree_path.join("conflicts-one.txt")).expect("read");
|
|
assert!(conflicts_one.contains("<<<<<<< HEAD"));
|
|
assert!(conflicts_one.contains(">>>>>>>"));
|
|
}
|