consta 1.0.4

Git contribution statistics tool
Documentation
use git2::{Repository, Signature};
use std::fmt::Write;
use std::path::PathBuf;
use tempfile::TempDir;

#[allow(dead_code)]
pub fn generate_test_repo(commits: usize, files_per_commit: usize) -> (TempDir, PathBuf) {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let repo = Repository::init(temp_dir.path()).expect("Failed to init repo");

    let sig = Signature::now("Benchmark Author", "bench@example.com")
        .expect("Failed to create signature");

    for i in 0..commits {
        for j in 0..files_per_commit {
            let filename = format!("src/file_{i}_{j}.rs");
            let workdir = repo.workdir().unwrap();
            let file_path = workdir.join(&filename);

            std::fs::create_dir_all(file_path.parent().unwrap()).ok();
            std::fs::write(
                &file_path,
                format!(
                    "// File {} content with some lines\nfn test() {{\n    let x = {};\n}}\n",
                    filename,
                    i * j
                ),
            )
            .ok();

            let mut index = repo.index().expect("Failed to get index");
            index.add_path(std::path::Path::new(&filename)).ok();
            let tree_id = index.write_tree().expect("Failed to write tree");
            let tree = repo.find_tree(tree_id).expect("Failed to find tree");

            let message = format!("Commit {i} with file {j}");
            repo.commit(Some("HEAD"), &sig, &sig, &message, &tree, &[]).ok();
        }
    }

    let path = temp_dir.path().to_path_buf();
    (temp_dir, path)
}

pub fn generate_git_log_output(commits: usize, files_per_commit: usize) -> String {
    let mut output = String::new();

    for i in 0..commits {
        let hash = format!("{:040x}", i * 12345);
        let timestamp = format!("2024-01-{:02}T10:30:00+00:00", (i % 28) + 1);
        let message = format!("Commit {i} with {files_per_commit} files");

        let _ = writeln!(output, "commit {hash}|{timestamp}|{message}");

        for j in 0..files_per_commit {
            let added = 10 + (i * j % 100);
            let deleted = 5 + (i * j % 50);
            let filename = format!("src/file_{i}_{j}.rs");
            let _ = writeln!(output, "{added}\t{deleted}\t{filename}");
        }
    }

    output
}

fn create_repo_at(path: &std::path::Path) {
    std::fs::create_dir_all(path).ok();
    let repo = Repository::init(path).expect("Failed to init repo");
    let sig = Signature::now("Test", "test@test.com").expect("Failed to create sig");

    let file_path = path.join("README.md");
    std::fs::write(&file_path, "# Test").ok();

    let mut index = repo.index().expect("Failed to get index");
    index.add_path(std::path::Path::new("README.md")).ok();
    let tree_id = index.write_tree().expect("Failed to write tree");
    let tree = repo.find_tree(tree_id).expect("Failed to find tree");

    repo.commit(Some("HEAD"), &sig, &sig, "Initial commit", &tree, &[]).ok();
}

fn recurse(path: &std::path::Path, remaining: usize, repo_count: &mut usize) {
    if *repo_count == 0 {
        return;
    }

    if remaining == 0 {
        return;
    }

    let dirs = remaining.min(3);
    for i in 0..dirs {
        if *repo_count == 0 {
            return;
        }

        let subdir = path.join(format!("subdir_{i}"));

        if remaining == 1 || *repo_count <= 2 {
            create_repo_at(&subdir);
            *repo_count -= 1;
        } else {
            recurse(&subdir, remaining - 1, repo_count);
        }
    }
}

pub fn generate_repo_tree(repo_count: usize, depth: usize) -> TempDir {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let root = temp_dir.path();

    let mut remaining = repo_count;
    recurse(root, depth, &mut remaining);

    temp_dir
}