git-snip 0.2.0

Snip local Git branches that do not exist on the remote.
Documentation
mod branch;
mod hook;
mod input;
mod reference;
mod remote;
mod repo;

use std::env;

use anyhow::{Context, Result};

use hook::{GitHook, GitHookType};

/// Delete all local branches that are not in remote branches.
pub fn run(no_confirm: bool, install_hook: bool) -> Result<()> {
    // Get current working directory
    let current_dir = env::current_dir().context("Could not get current directory")?;
    let repo = repo::Repository::open(current_dir)?;

    // Install the hook script if requested.
    if install_hook {
        println!("Installing git-snip hook script.");
        let hook = GitHook::default();
        repo.install_hook(&hook, GitHookType::PostMerge)?;
        repo.install_hook(&hook, GitHookType::PostRewrite)?;
    }

    let mut branches_to_delete = repo.orphaned_branches();
    if branches_to_delete.is_empty() {
        println!("No local branches to delete.");
        return Ok(());
    }

    if !no_confirm {
        println!("Local branches to delete:");
        for branch in &branches_to_delete {
            println!("  - {branch}");
        }

        let user_input = input::prompt_stdin("Delete these branches? (y/n): ");
        if user_input != "y" && user_input != "yes" {
            println!("Aborting.");
            return Ok(());
        }
    }

    for branch in &mut branches_to_delete {
        println!("Deleting branch: {branch}");
        branch.delete()?;
    }

    Ok(())
}

#[cfg(test)]
pub mod test_utilities {
    use git2::{Repository, RepositoryInitOptions};
    use tempfile::TempDir;

    /// Create a mock Git repository with initial commit in a temporary
    /// directory for testing.
    pub fn create_mock_repo() -> (TempDir, Repository) {
        let tempdir = TempDir::new().unwrap();
        let mut opts = RepositoryInitOptions::new();
        opts.initial_head("main");
        let repo = Repository::init_opts(tempdir.path(), &opts).unwrap();

        // Create initial commit
        {
            let mut config = repo.config().unwrap();
            config.set_str("user.name", "name").unwrap();
            config.set_str("user.email", "email").unwrap();
            let mut index = repo.index().unwrap();
            let id = index.write_tree().unwrap();

            let tree = repo.find_tree(id).unwrap();
            let sig = repo.signature().unwrap();
            repo.commit(Some("HEAD"), &sig, &sig, "initial\n\nbody", &tree, &[])
                .unwrap();
        }
        (tempdir, repo)
    }

    /// Find the latest commit in a repository.
    pub fn get_latest_commit(repo: &git2::Repository) -> git2::Commit {
        let head = repo.head().unwrap();
        let commit = head.peel_to_commit().unwrap();
        commit
    }
}