weavatrix-git 0.3.1

Fast, bounded, evidence-carrying Git reader with an optional read-only MCP server
Documentation
use std::{
    env,
    io::{self, Write},
    path::PathBuf,
    process::ExitCode,
};

use weavatrix_git::{HistoryOptions, Repository};

fn main() -> ExitCode {
    match run(env::args().skip(1).collect()) {
        Ok(()) => ExitCode::SUCCESS,
        Err(error) => {
            eprintln!("weavatrix-git: {error}");
            ExitCode::FAILURE
        }
    }
}

fn run(mut args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
    let mut repository = PathBuf::from(".");
    if args.first().is_some_and(|value| value == "-C") {
        if args.len() < 3 {
            return Err("usage: -C requires a repository path and command".into());
        }
        repository = PathBuf::from(args.remove(1));
        args.remove(0);
    }
    let command = args.first().map_or("head", String::as_str);
    let repo = Repository::open(repository)?;
    match command {
        "head" => {
            let head = repo.head()?;
            if let Some(id) = head.target {
                println!("{id}");
            } else {
                println!("unborn");
            }
        }
        "cat" => {
            let spec = args.get(1).ok_or("usage: cat <object>")?;
            let object = repo.object(repo.resolve(spec)?)?;
            println!("{} {}", object.kind.as_str(), object.data.len());
            io::stdout().write_all(&object.data)?;
        }
        "log" => {
            let spec = args.get(1).map_or("HEAD", String::as_str);
            let max_commits = args
                .get(2)
                .map(|value| value.parse())
                .transpose()?
                .unwrap_or(100);
            let options = HistoryOptions {
                max_commits,
                ..HistoryOptions::default()
            };
            for record in repo.history(repo.resolve(spec)?, options)? {
                let time = record
                    .commit
                    .committer
                    .as_ref()
                    .map_or(0, |signature| signature.timestamp);
                println!(
                    "{}\t{}\t{}",
                    record.id,
                    time,
                    record.commit.summary_lossy().replace('\t', " ")
                );
            }
        }
        "diff" => {
            let old = args.get(1).ok_or("usage: diff <old> <new>")?;
            let new = args.get(2).ok_or("usage: diff <old> <new>")?;
            for change in repo.diff_commits(repo.resolve(old)?, repo.resolve(new)?)? {
                println!(
                    "{:?}\t{}",
                    change.kind,
                    String::from_utf8_lossy(&change.path)
                );
            }
        }
        _ => return Err(format!("unknown command {command:?}").into()),
    }
    Ok(())
}