grit-lib 0.5.21

Core library for the grit Git implementation
//! `--stat` / `--name-only` / `--name-status` compatibility with system `git`.

use std::sync::atomic::AtomicUsize;
use std::sync::Arc;

use grit_lib::diff::{build_file_diffs, DiffDetail, DiffOptions, DiffTarget};
use grit_lib::objects::{ObjectId, ObjectKind, TreeEntry};
use grit_lib::parallel::ThreadPolicy;
use grit_lib::repo::{InitOptions, Repository};

fn init_repo() -> (tempfile::TempDir, Repository) {
    let dir = tempfile::tempdir().expect("tempdir");
    let repo = Repository::init(dir.path(), &InitOptions::default()).expect("init");
    (dir, repo)
}

fn write_blob(repo: &Repository, content: &[u8]) -> grit_lib::objects::ObjectId {
    repo.odb()
        .write(ObjectKind::Blob, content)
        .expect("write blob")
}

fn tree_with(repo: &Repository, entries: &[(&str, &[u8])]) -> grit_lib::objects::ObjectId {
    let mut tes = Vec::new();
    for (name, data) in entries {
        let oid = write_blob(repo, data);
        tes.push(TreeEntry {
            mode: 0o100644,
            name: name.as_bytes().to_vec(),
            oid,
        });
    }
    let data = grit_lib::objects::serialize_tree(&tes);
    repo.odb()
        .write(ObjectKind::Tree, &data)
        .expect("write tree")
}

fn grit_numstat_for_trees(
    repo: &Repository,
    old: Option<&grit_lib::objects::ObjectId>,
    new: Option<&grit_lib::objects::ObjectId>,
) -> Vec<(usize, usize, String)> {
    let entries = repo.diff().tree_to_tree(old, new).expect("tree diff");
    let config = repo.config().expect("config");
    let out = build_file_diffs(
        repo.odb(),
        entries,
        DiffDetail::Stat,
        DiffOptions::default(),
        config.as_ref(),
        &ThreadPolicy::default(),
    )
    .expect("stat diff");
    out.files
        .into_iter()
        .map(|f| {
            let stat = f.stat.unwrap_or_default();
            (stat.insertions, stat.deletions, f.path)
        })
        .collect()
}

#[test]
fn diff_stat_counts_match_git_numstat_trees_text_and_binary() {
    let (_dir, repo) = init_repo();
    let old = tree_with(&repo, &[("a.txt", b"one\n")]);
    let new = tree_with(
        &repo,
        &[("a.txt", b"one\ntwo\n"), ("bin.dat", &[0, 1, 2, 3])],
    );
    let grit = grit_numstat_for_trees(&repo, Some(&old), Some(&new));
    assert!(grit.iter().any(|(_, _, p)| p == "a.txt"));
    assert!(grit
        .iter()
        .any(|(i, d, p)| p == "a.txt" && *i == 1 && *d == 0));
    assert!(grit
        .iter()
        .any(|(i, d, p)| p == "bin.dat" && *i == 0 && *d == 0));
}

#[test]
fn identical_binary_rename_keeps_binary_flag_in_stat_mode() {
    let (_dir, repo) = init_repo();
    let old = tree_with(&repo, &[("f.txt", b"alpha\n")]);
    let new = tree_with(&repo, &[("f.txt", b"beta\n")]);
    let entries = repo
        .diff()
        .tree_to_tree(Some(&old), Some(&new))
        .expect("entries");
    let counter = Arc::new(AtomicUsize::new(0));
    let config = repo.config().expect("config");
    let mut opts = DiffOptions::default();
    opts.blob_read_counter = Some(Arc::clone(&counter));
    let _ = build_file_diffs(
        repo.odb(),
        entries,
        DiffDetail::NameOnly,
        opts,
        config.as_ref(),
        &ThreadPolicy::default(),
    )
    .expect("name-only");
    assert_eq!(counter.load(std::sync::atomic::Ordering::Relaxed), 0);
    let new_tree = tree_with(&repo, &[("f", b"x\n")]);
    let _target = DiffTarget::Trees {
        old: None,
        new: new_tree,
    };
}