use std::{
fs,
path::{Path, PathBuf},
process::Command,
sync::atomic::{AtomicU64, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
use weavatrix_git::{GitError, HistoryOptions, Limits, Repository};
struct Layouts {
root: PathBuf,
source: PathBuf,
bare: PathBuf,
linked: PathBuf,
}
static FIXTURE_SEQUENCE: AtomicU64 = AtomicU64::new(0);
impl Layouts {
fn new() -> Self {
let unique = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let root = std::env::temp_dir().join(format!(
"weavatrix-layout-{}-{unique}-{}",
std::process::id(),
FIXTURE_SEQUENCE.fetch_add(1, Ordering::Relaxed)
));
let source = root.join("source");
let bare = root.join("bare.git");
let linked = root.join("linked");
fs::create_dir_all(&source).unwrap();
git(&source, &["init", "-q"]);
git(&source, &["config", "user.name", "Weavatrix Test"]);
git(&source, &["config", "user.email", "test@weavatrix.local"]);
fs::write(source.join("evidence.txt"), "evidence").unwrap();
git(&source, &["add", "evidence.txt"]);
git(&source, &["commit", "-q", "-m", "initial"]);
git(
&root,
&[
"clone",
"-q",
"--bare",
path_text(&source),
path_text(&bare),
],
);
git(
&source,
&["worktree", "add", "-q", "-b", "linked", path_text(&linked)],
);
Self {
root,
source,
bare,
linked,
}
}
}
impl Drop for Layouts {
fn drop(&mut self) {
fs::remove_dir_all(&self.root).ok();
}
}
#[test]
fn discovers_bare_and_linked_worktree_layouts() {
let fixture = Layouts::new();
let source = Repository::open(&fixture.source).unwrap();
let expected = source.resolve("HEAD").unwrap();
let bare = Repository::open(&fixture.bare).unwrap();
assert!(bare.work_dir().is_none());
assert_eq!(bare.resolve("HEAD").unwrap(), expected);
assert_eq!(bare.commit(expected).unwrap().summary_lossy(), "initial");
assert!(bare.status().is_err());
let linked = Repository::open(&fixture.linked).unwrap();
let linked_path = fixture.linked.canonicalize().unwrap();
assert_eq!(linked.work_dir(), Some(linked_path.as_path()));
assert_ne!(linked.git_dir(), linked.common_dir());
assert_eq!(linked.resolve("HEAD").unwrap(), expected);
git(&fixture.source, &["checkout", "-q", "--detach"]);
let detached = Repository::open(&fixture.source).unwrap().head().unwrap();
assert!(detached.symbolic.is_none());
assert_eq!(detached.target, Some(expected));
assert!(Repository::open(&fixture.root).is_err());
}
#[test]
fn enforces_configured_object_limit() {
let fixture = Layouts::new();
let repository = Repository::open_with_limits(
&fixture.source,
Limits {
max_object_bytes: 8,
..Limits::default()
},
)
.unwrap();
let error = repository
.commit(repository.resolve("HEAD").unwrap())
.unwrap_err();
assert!(matches!(error, GitError::LimitExceeded { .. }));
let repository = Repository::open_with_limits(
&fixture.source,
Limits {
max_history_commits: 1,
..Limits::default()
},
)
.unwrap();
let error = repository
.history(
repository.resolve("HEAD").unwrap(),
HistoryOptions {
max_commits: 2,
..HistoryOptions::default()
},
)
.unwrap_err();
assert!(matches!(error, GitError::LimitExceeded { .. }));
}
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 path_text(path: &Path) -> &str {
path.to_str().expect("test path is Unicode")
}