use std::{
collections::BTreeSet,
fs,
path::{Path, PathBuf},
process::Command,
sync::atomic::{AtomicU64, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
use weavatrix_git::{PathBloom, Repository};
struct Fixture {
path: PathBuf,
}
static FIXTURE_SEQUENCE: AtomicU64 = AtomicU64::new(0);
impl Fixture {
fn new() -> Self {
let unique = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let path = std::env::temp_dir().join(format!(
"weavatrix-storage-{}-{unique}-{}",
std::process::id(),
FIXTURE_SEQUENCE.fetch_add(1, Ordering::Relaxed)
));
fs::create_dir_all(&path).unwrap();
git(&path, &["init", "-q"]);
git(&path, &["config", "user.name", "Weavatrix Test"]);
git(&path, &["config", "user.email", "test@weavatrix.local"]);
Self { path }
}
fn commit(&self, revision: usize) {
fs::write(
self.path.join("tracked.txt"),
format!("revision={revision}\n"),
)
.unwrap();
git(&self.path, &["add", "-A"]);
git(
&self.path,
&["commit", "-q", "-m", &format!("revision {revision}")],
);
}
}
impl Drop for Fixture {
fn drop(&mut self) {
fs::remove_dir_all(&self.path).ok();
}
}
#[test]
fn reads_split_commit_graph_and_changed_path_bloom() {
let fixture = Fixture::new();
fixture.commit(0);
fixture.commit(1);
git(
&fixture.path,
&[
"commit-graph",
"write",
"--reachable",
"--changed-paths",
"--split=no-merge",
],
);
fixture.commit(2);
fixture.commit(3);
git(
&fixture.path,
&[
"commit-graph",
"write",
"--reachable",
"--changed-paths",
"--split=no-merge",
],
);
git(&fixture.path, &["commit-graph", "verify"]);
let repository = Repository::open(&fixture.path).unwrap();
assert!(repository.commit_graph_layer_count() >= 2);
let head = repository.resolve("HEAD").unwrap();
assert_eq!(
repository
.commit_maybe_changed_path(head, b"tracked.txt")
.unwrap(),
Some(PathBloom::Maybe)
);
assert_eq!(
repository
.commit_maybe_changed_path(head, b"never-created.txt")
.unwrap(),
Some(PathBloom::DefinitelyNot)
);
assert_eq!(repository.commit_metadata(head).unwrap().parents.len(), 1);
}
#[test]
fn routes_objects_through_multi_pack_index() {
let fixture = Fixture::new();
fixture.commit(0);
git(&fixture.path, &["repack", "-q", "-d", "-l"]);
fixture.commit(1);
git(&fixture.path, &["repack", "-q", "-d", "-l"]);
git(&fixture.path, &["multi-pack-index", "write", "--bitmap"]);
git(&fixture.path, &["multi-pack-index", "verify"]);
let repository = Repository::open(&fixture.path).unwrap();
assert!(repository.pack_count() >= 2);
assert_eq!(repository.multi_pack_index_count(), 1);
let head = repository.resolve("HEAD").unwrap();
assert_eq!(
repository.commit(head).unwrap().summary_lossy(),
"revision 1"
);
assert!(repository.contains(head));
let reachable = repository
.bitmap_reachable(head)
.unwrap()
.expect("Git wrote a MIDX bitmap")
.into_iter()
.map(|id| id.to_string())
.collect::<BTreeSet<_>>();
let expected = git_output(&fixture.path, &["rev-list", "--objects", "HEAD"])
.lines()
.filter_map(|line| line.split_ascii_whitespace().next())
.map(str::to_owned)
.collect::<BTreeSet<_>>();
assert_eq!(reachable, expected);
let ids = repository
.history_ids(
head,
weavatrix_git::HistoryOptions {
max_commits: 2,
..weavatrix_git::HistoryOptions::default()
},
)
.unwrap();
let parallel = repository.commit_metadata_parallel(&ids);
assert_eq!(parallel.len(), 2);
assert_eq!(parallel[0].as_ref().unwrap().id, ids[0]);
}
#[test]
fn reads_pack_reachability_bitmap() {
let fixture = Fixture::new();
fixture.commit(0);
fixture.commit(1);
fixture.commit(2);
git(&fixture.path, &["repack", "-q", "-a", "-d", "-b"]);
let repository = Repository::open(&fixture.path).unwrap();
assert_eq!(repository.multi_pack_index_count(), 0);
let head = repository.resolve("HEAD").unwrap();
let reachable = repository
.bitmap_reachable(head)
.unwrap()
.expect("Git wrote a pack bitmap")
.into_iter()
.map(|id| id.to_string())
.collect::<BTreeSet<_>>();
let expected = git_output(&fixture.path, &["rev-list", "--objects", "HEAD"])
.lines()
.filter_map(|line| line.split_ascii_whitespace().next())
.map(str::to_owned)
.collect::<BTreeSet<_>>();
assert_eq!(reachable, expected);
}
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()
}