supgit 0.2.0

A simple Git CLI wrapper for common Git operations
use std::env;
use std::path::Path;

use anyhow::{bail, Context, Result};

use crate::git::run_git_silent;

pub fn run_clone(url: &str, directory: Option<&str>) -> Result<()> {
    println!("→ Cloning {}...", url);

    let mut args = vec!["clone", url];
    let target_dir = directory.map(String::from);

    if let Some(ref dir) = target_dir {
        args.push(dir.as_str());
    }

    run_git_silent(&args)?;

    let repo_dir = determine_repo_dir(url, target_dir.as_deref())?;

    println!("✓ Clone complete");
    println!("→ Changing directory to {}...", repo_dir.display());

    print_cd_script(&repo_dir)?;

    Ok(())
}

fn determine_repo_dir(url: &str, directory: Option<&str>) -> Result<std::path::PathBuf> {
    if let Some(dir) = directory {
        let path = Path::new(dir);
        if path.is_absolute() {
            return Ok(path.to_path_buf());
        }
        let cwd = env::current_dir().context("failed to get current directory")?;
        return Ok(cwd.join(path));
    }

    let repo_name = extract_repo_name(url)?;
    let cwd = env::current_dir().context("failed to get current directory")?;
    Ok(cwd.join(&repo_name))
}

fn extract_repo_name(url: &str) -> Result<String> {
    let url = url.trim_end_matches('/');

    let name = if url.starts_with("git@") {
        url.rsplit(':')
            .next()
            .and_then(|s| s.rsplit('/').next())
            .map(|s| s.trim_end_matches(".git").to_string())
    } else if url.starts_with("https://") || url.starts_with("http://") {
        url.rsplit('/')
            .next()
            .map(|s| s.trim_end_matches(".git").to_string())
    } else if url.starts_with("ssh://") {
        url.rsplit('/')
            .next()
            .map(|s| s.trim_end_matches(".git").to_string())
    } else {
        url.rsplit('/')
            .next()
            .map(|s| s.trim_end_matches(".git").to_string())
    };

    name.or_else(|| {
        let name = url.rsplit('/').next()?;
        Some(name.trim_end_matches(".git").to_string())
    })
    .ok_or_else(|| anyhow::anyhow!("could not determine repository name from URL"))
}

fn print_cd_script(repo_dir: &Path) -> Result<()> {
    let cwd = env::current_dir().context("failed to get current directory")?;

    if repo_dir.exists() && repo_dir.starts_with(&cwd) {
        let relative = repo_dir.strip_prefix(&cwd).unwrap_or(repo_dir);
        println!();
        println!("To change into the cloned repository, run:");
        println!("  cd {}", relative.display());
    } else if repo_dir.exists() {
        println!();
        println!("To change into the cloned repository, run:");
        println!("  cd {}", repo_dir.display());
    } else {
        bail!(
            "cloned repository directory not found at {}",
            repo_dir.display()
        );
    }

    Ok(())
}