weavatrix-git 0.3.2

Fast, bounded, evidence-carrying Git reader with an optional read-only MCP server
Documentation
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")
}