treeflow 0.2.1

CLI tool for simplified Git worktree management to speed up switching contexts when working collaboratively.
Documentation
use crate::utils::errors::CustomError;
use std::path::PathBuf;
use std::process::{Command, Output};

pub fn branch_exists<P: AsRef<str>>(branch: P) -> Result<bool, CustomError> {
    match git(&["show-ref", "--quiet", branch.as_ref()]) {
        Ok(_) => Ok(true),
        Err(err) =>
            match err {
                CustomError::ProcessFailed(Some(1), _, _, _) => Ok(false),
                _ => Err(err)
            }
    }
}

pub fn checkout_worktree<P: AsRef<str>>(dir: &PathBuf, branch: P) -> Result<Output, CustomError> {
    let dir_str = dir.to_str().ok_or(CustomError::Custom(dir.display().to_string() + " was not a UTF-8 string."))?;
    git(&["worktree", "add", dir_str, branch.as_ref()])
}

pub fn new_worktree<P: AsRef<str>>(dir: &PathBuf, branch: P) -> Result<Output, CustomError> {
    let dir_str = dir.to_str().ok_or(CustomError::Custom(dir.display().to_string() + " was not a UTF-8 string."))?;
    git(&["worktree", "add", dir_str, "-b", branch.as_ref()])
}

pub fn checkout_new_or_existing_worktree<P: AsRef<str>>(dir: &PathBuf, branch: P) -> Result<Output, CustomError> {
    if branch_exists(&branch)? {
        checkout_worktree(dir, &branch)
    } else {
        new_worktree(dir, &branch)
    }
}

/// Checks if a directory is a valid Git worktree (has .git file) or repository (has .git directory)
pub fn is_valid_worktree(path: &PathBuf) -> bool {
    path.exists() && path.join(".git").exists()
}

pub fn list_branches(pattern: &str) -> Result<Vec<String>, CustomError> {
    let output = git(&["branch", "-l", &format!("{}*", pattern)])?;
    let branches: Vec<String> = str::from_utf8(&output.stdout)?.to_string()
        .lines()
        .map(|line| line.trim_start_matches("* ").trim_start_matches("+ ").trim_start_matches(" ").trim_end_matches(" ").to_string())
        .collect();
    Ok(branches)
}

pub fn remove_worktree(name: &str, force: bool) -> Result<Output, CustomError> {
    let mut args = vec!["worktree", "remove", name];
    if force {
        args.push("--force");
    }
    git(&args)
}

pub fn delete_branch(branch: &str, force: bool) -> Result<Output, CustomError> {
    let mut args = vec!["branch", "-D", branch];
    if !force {
        args = vec!["branch", "-d", branch];
    }
    git(&args)
}

fn git(args: &[&str]) -> Result<Output, CustomError> {
    let output = Command::new("git")
        .args(args)
        .output()?;
    match output.status.success() {
        true => Ok(output),
        false => Err(CustomError::ProcessFailed(output.status.code(), "git".to_string(), std::str::from_utf8(&output.stdout).map(|str| str.to_string())?, std::str::from_utf8(&output.stderr).map(|str| str.to_string())?))
    }
}