use std::{
fs,
path::{Path, PathBuf},
process::Command,
sync::atomic::{AtomicU64, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
use weavatrix_git::{Repository, WorktreeKind, WorktreeSafetyLevel};
struct Fixture {
path: PathBuf,
}
static 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-safety-{}-{unique}-{}",
std::process::id(),
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"]);
fs::write(path.join("tracked.txt"), "tracked").unwrap();
git(&path, &["add", "tracked.txt"]);
git(&path, &["commit", "-q", "-m", "initial"]);
Self { path }
}
}
impl Drop for Fixture {
fn drop(&mut self) {
fs::remove_dir_all(&self.path).ok();
}
}
#[test]
fn clean_repository_is_clean() {
let fixture = Fixture::new();
let safety = Repository::open(&fixture.path)
.unwrap()
.worktree_safety()
.unwrap();
assert_eq!(safety.level, WorktreeSafetyLevel::Clean);
assert_eq!(safety.kind, WorktreeKind::Primary);
assert!(!safety.tracked_dirty);
assert_eq!(safety.untracked_count, 0);
}
#[test]
fn modified_tracked_file_is_dirty() {
let fixture = Fixture::new();
fs::write(fixture.path.join("tracked.txt"), "changed").unwrap();
let safety = Repository::open(&fixture.path)
.unwrap()
.worktree_safety()
.unwrap();
assert_eq!(safety.level, WorktreeSafetyLevel::DirtyTracked);
assert!(safety.tracked_dirty);
}
#[test]
fn staged_file_is_staged_and_dirty() {
let fixture = Fixture::new();
fs::write(fixture.path.join("extra.txt"), "staged").unwrap();
git(&fixture.path, &["add", "extra.txt"]);
let safety = Repository::open(&fixture.path)
.unwrap()
.worktree_safety()
.unwrap();
assert!(safety.staged_dirty);
assert_eq!(safety.level, WorktreeSafetyLevel::DirtyTracked);
}
#[test]
fn untracked_file_is_has_untracked() {
let fixture = Fixture::new();
fs::write(fixture.path.join("scratch.txt"), "scratch").unwrap();
let safety = Repository::open(&fixture.path)
.unwrap()
.worktree_safety()
.unwrap();
assert_eq!(safety.level, WorktreeSafetyLevel::HasUntracked);
assert_eq!(safety.untracked_count, 1);
assert!(!safety.tracked_dirty);
}
#[test]
fn gitignore_only_is_ignored_only() {
let fixture = Fixture::new();
fs::write(fixture.path.join(".gitignore"), "tmp.log\n").unwrap();
git(&fixture.path, &["add", ".gitignore"]);
git(&fixture.path, &["commit", "-q", "-m", "ignore"]);
fs::write(fixture.path.join("tmp.log"), "noise").unwrap();
let safety = Repository::open(&fixture.path)
.unwrap()
.worktree_safety()
.unwrap();
assert_eq!(safety.level, WorktreeSafetyLevel::IgnoredOnly);
assert_eq!(safety.ignored_count, 1);
assert_eq!(safety.untracked_count, 0);
}
#[test]
fn info_exclude_is_honored() {
let fixture = Fixture::new();
let exclude = fixture.path.join(".git").join("info").join("exclude");
fs::create_dir_all(exclude.parent().unwrap()).unwrap();
fs::write(&exclude, "local.bin\n").unwrap();
fs::write(fixture.path.join("local.bin"), "x").unwrap();
let safety = Repository::open(&fixture.path)
.unwrap()
.worktree_safety()
.unwrap();
assert_eq!(safety.level, WorktreeSafetyLevel::IgnoredOnly);
}
#[test]
fn bare_repository_is_unknown() {
let fixture = Fixture::new();
let bare = fixture.path.join("bare.git");
git(
&fixture.path,
&["clone", "-q", "--bare", ".", path_text(&bare)],
);
let safety = Repository::open(&bare).unwrap().worktree_safety().unwrap();
assert_eq!(safety.kind, WorktreeKind::Bare);
assert_eq!(safety.level, WorktreeSafetyLevel::Unknown);
}
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("utf-8 fixture path")
}