use std::{
fs,
path::{Path, PathBuf},
process::Command,
sync::atomic::{AtomicU64, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
use weavatrix_git::{ChangeKind, HistoryOptions, RepositorySet};
struct Fixture {
root: PathBuf,
left: PathBuf,
right: PathBuf,
shared: String,
}
static FIXTURE_SEQUENCE: AtomicU64 = AtomicU64::new(0);
impl Fixture {
fn new() -> Self {
let unique = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let root = std::env::temp_dir().join(format!(
"weavatrix-set-{}-{unique}-{}",
std::process::id(),
FIXTURE_SEQUENCE.fetch_add(1, Ordering::Relaxed)
));
let left = root.join("left");
let right = root.join("right");
fs::create_dir_all(&left).unwrap();
git(&left, &["init", "-q"]);
configure(&left);
fs::write(left.join("shared.txt"), "shared").unwrap();
commit(&left, "shared");
let shared = git_output(&left, &["rev-parse", "HEAD"]).trim().to_owned();
git(&root, &["clone", "-q", path_text(&left), path_text(&right)]);
configure(&right);
fs::write(left.join("left.txt"), "left").unwrap();
commit(&left, "left");
fs::write(right.join("right.txt"), "right").unwrap();
commit(&right, "right");
for path in [&left, &right] {
git(path, &["commit-graph", "write", "--reachable"]);
}
Self {
root,
left,
right,
shared,
}
}
}
impl Drop for Fixture {
fn drop(&mut self) {
fs::remove_dir_all(&self.root).ok();
}
}
#[test]
fn correlates_histories_objects_and_diffs_across_repositories() {
let fixture = Fixture::new();
let set = RepositorySet::open([
("left", fixture.left.as_path()),
("right", fixture.right.as_path()),
])
.unwrap();
assert_eq!(set.len(), 2);
assert!(!set.is_empty());
let left = set.id("left").unwrap();
let right = set.id("right").unwrap();
assert_eq!(set.name(left), Some("left"));
assert!(set.repository(left).is_some());
let options = HistoryOptions {
max_commits: 10,
..HistoryOptions::default()
};
let serial = set.histories(options).unwrap();
let parallel = set.histories_parallel(options).unwrap();
assert_eq!(serial[0].commits, parallel[0].commits);
assert_eq!(serial[1].commits, parallel[1].commits);
assert_eq!(set.resolve(left, "HEAD").unwrap(), serial[0].head);
let shared = fixture.shared.parse().unwrap();
assert_eq!(set.find_object(shared), vec![left, right]);
let common = set.shared_commits(options).unwrap();
assert!(
common
.iter()
.any(|commit| { commit.id == shared && commit.repositories == vec![left, right] })
);
let changes = set.diff_commits(left, "HEAD", right, "HEAD").unwrap();
assert!(
changes
.iter()
.any(|change| { change.path == b"left.txt" && change.kind == ChangeKind::Deleted })
);
assert!(
changes
.iter()
.any(|change| { change.path == b"right.txt" && change.kind == ChangeKind::Added })
);
}
#[test]
fn rejects_invalid_repository_names_and_ids() {
let fixture = Fixture::new();
assert!(
RepositorySet::open([
("same", fixture.left.as_path()),
("same", fixture.right.as_path()),
])
.is_err()
);
assert!(RepositorySet::open([("bad:name", fixture.left.as_path())]).is_err());
let empty = RepositorySet::open(std::iter::empty::<(&str, &Path)>()).unwrap();
assert!(empty.is_empty());
}
fn configure(path: &Path) {
git(path, &["config", "user.name", "Weavatrix Test"]);
git(path, &["config", "user.email", "test@weavatrix.local"]);
}
fn commit(path: &Path, message: &str) {
git(path, &["add", "-A"]);
git(path, &["commit", "-q", "-m", message]);
}
fn git(path: &Path, args: &[&str]) {
let output = Command::new("git")
.args(args)
.current_dir(path)
.output()
.unwrap();
assert!(
output.status.success(),
"git {args:?}: {}",
String::from_utf8_lossy(&output.stderr)
);
}
fn git_output(path: &Path, args: &[&str]) -> String {
let output = Command::new("git")
.args(args)
.current_dir(path)
.output()
.unwrap();
assert!(output.status.success());
String::from_utf8(output.stdout).unwrap()
}
fn path_text(path: &Path) -> &str {
path.to_str().expect("test path is Unicode")
}