thin-fetch 0.1.0

Low-level Git crate to fetch objects with high granularity
Documentation
use clap::{Arg, Command};
use thin_fetch::{HashKind, Repo};

fn main() {
    let args = Command::new("aports")
        .arg(Arg::new("HEAD").required(true).help("Current HEAD commit"))
        .get_matches_from(std::env::args());

    let head = args.get_one::<String>("HEAD").unwrap();

    let mut repo = Repo::new(
        "https://gitlab.alpinelinux.org/alpine/aports.git",
        HashKind::Sha1,
    );

    // fetch head

    eprintln!(
        "[DEBUG] ========== fetching {} (depth=1) ==========",
        &head[0..12]
    );

    repo.fetch_shallow(head).expect("could not fetch shallow");

    eprintln!("[DEBUG] ========== END ==========");

    // get master ref

    eprintln!("[DEBUG] getting ref for refs/heads/master");

    let master_ref = repo
        .get_ref("refs/heads/master")
        .expect("could not get master")
        .expect("master not found");
    let master = master_ref.target();

    eprintln!("[DEBUG] new master ref: {master}");

    // fetch history

    eprintln!(
        "[DEBUG] ========== fetching {}..{} ==========",
        &head[0..12],
        &master[0..12]
    );

    repo.fetch_history_linear(head, master, true)
        .expect("could not fetch history");

    eprintln!("[DEBUG] ========== END ==========");

    // print history

    let history = repo
        .get_history_linear(head, master)
        .expect("could not get history");

    for i in 0..history.len() {
        let Some(checkout) = history.get(i) else {
            break;
        };

        let hdr = checkout.commit.header();
        println!("{hdr}");

        let Some(next) = history.get(i + 1) else {
            println!(" first commit, no diff available");
            continue;
        };

        let diff = &checkout.get_diff(next);
        for object in diff {
            let old_obj = object.old_oid.as_deref().map(|oid| repo.get_blob(oid));
            let new_obj = object.new_oid.as_deref().map(|oid| repo.get_blob(oid));

            let path = object
                .path
                .strip_prefix("./")
                .unwrap_or_else(|| &object.path);

            let status = match (old_obj, new_obj) {
                (Some(_), Some(Some(new_blob))) => {
                    format!("changed ({} bytes)", new_blob.content.len())
                }
                (Some(_), Some(None)) => "changed (file not found)".to_owned(),

                (None, Some(Some(new_blob))) => format!("added ({} bytes)", new_blob.content.len()),
                (None, Some(None)) => "added (file not found)".to_owned(),

                (Some(_), None) => "removed".to_owned(),
                (None, None) => "??????".to_owned(),
            };

            println!(" {path} | {status}");
        }
        println!(" {} file(s) changed", diff.len());
        println!();
    }
}