treeflow 0.2.1

CLI tool for simplified Git worktree management to speed up switching contexts when working collaboratively.
Documentation
use assert_cmd::assert::OutputAssertExt;
use regex::Regex;
use std::path::{Path, PathBuf};
use std::process::Command;

pub const PRIMARY_BRANCH: &str = "master";
pub const TEST_EMAIL: &str = "test@example.com";
pub const TEST_NAME: &str = "Test User";
pub const README_CONTENT: &str = "Test repo";

pub struct GitCommand {
}

#[allow(dead_code)]
impl GitCommand {
    /// Sets up a basic git repository with initial commit
    pub fn init(repo_dir: &Path) {
        Command::new("git")
            .args(["init", "--initial-branch", PRIMARY_BRANCH])
            .current_dir(repo_dir)
            .output()
            .expect("Failed to initialize git repository");

        Command::new("git")
            .args(["config", "user.email", TEST_EMAIL])
            .current_dir(repo_dir)
            .output()
            .expect("Failed to configure git email");

        Command::new("git")
            .args(["config", "user.name", TEST_NAME])
            .current_dir(repo_dir)
            .output()
            .expect("Failed to configure git name");

        std::fs::write(repo_dir.join("README.md"), README_CONTENT).expect("Failed to create README");

        Command::new("git")
            .args(["add", "README.md"])
            .current_dir(repo_dir)
            .output()
            .expect("Failed to add README to git");

        Command::new("git")
            .args(["commit", "-m", "Initial commit"])
            .current_dir(repo_dir)
            .output()
            .expect("Failed to commit initial changes");
    }

    /// Creates a new branch in the given repository
    pub fn create_branch(repo_dir: &Path, branch_name: &str) {
        Command::new("git")
            .args(["branch", branch_name])
            .current_dir(repo_dir)
            .output()
            .expect(&format!("Failed to create branch {}", branch_name));
    }

    /// Return a flag indicating whether the repository is checkout out onto the provided branch
    pub fn is_on_branch(repo_dir: &Path, expected_branch_name: &str) -> bool {
        let output = Command::new("git")
            .args(["branch", "--show-current"])
            .current_dir(repo_dir)
            .output()
            .expect("Failed to get current branch");

        let actual_branch_name = String::from_utf8(output.stdout)
            .expect("Branch name should be valid UTF-8")
            .trim()
            .to_string();

        actual_branch_name == expected_branch_name
    }

    /// Creates a new branch in the given repository
    pub fn create_worktree(repo_dir: &Path, branch_name: &str, worktree_dir: &Path) {
        Command::new("git")
            .args(["worktree", "add", "-b", branch_name, &worktree_dir.to_str().expect("worktree path should be representable as a string")])
            .current_dir(repo_dir)
            .assert()
            .success();
        ()
    }

    // Returns a bool indicating whether a work tree exists for a given branch name
    pub fn worktree_exists(repository_dir_buf: &PathBuf, worktree_dir_buf: &PathBuf) -> bool {
        let output = Command::new("git")
            .args(["worktree", "list", "--porcelain"])
            .current_dir(repository_dir_buf)
            .output()
            .expect("Failed to list worktrees");

        if !output.status.success() {
            return false;
        }

        let worktree_list = String::from_utf8_lossy(&output.stdout);
        let lines: Vec<&str> = worktree_list.lines().collect();
        let re = Regex::new(r"worktree (.+)").unwrap();

        let mut i = 0;
        while i < lines.len() {
            if let Some(captures) = re.captures(lines[i]) {
                let path_str = captures.get(1).unwrap().as_str();
                if worktree_dir_buf == &PathBuf::from(path_str) {
                    return true;
                }
            }

            i += 1;
        }

        false
    }

    /// Returns a bool indicating whether a branch exists in the repository
    pub fn branch_exists(repo_dir: &Path, branch_name: &str) -> bool {
        let output = Command::new("git")
            .args(["branch", "-l", branch_name])
            .current_dir(repo_dir)
            .output()
            .expect("Failed to list branches");

        if !output.status.success() {
            return false;
        }

        let branch_list = String::from_utf8_lossy(&output.stdout);
        let lines: Vec<&str> = branch_list.lines().collect();
        let re = Regex::new(r"^\s*\+\s*(.+)$").unwrap();

        let mut i = 0;
        while i < lines.len() {
            if let Some(captures) = re.captures(lines[i]) {
                let actual_branch_name = captures.get(1).unwrap().as_str().trim();
                if actual_branch_name == branch_name {
                    return true;
                }
            }
            i += 1;
        }

        false
    }
}