treeflow 0.2.1

CLI tool for simplified Git worktree management to speed up switching contexts when working collaboratively.
Documentation
#[cfg(test)]
mod utils;

#[cfg(test)]
mod finish_tests {
    use crate::utils::git_command::GitCommand;
    use crate::utils::treeflow_command::TreeflowCommand;
    use std::fs;
    use std::path::PathBuf;
    use tempdir::TempDir;
    use treeflow::utils::Return;

    const TEST_BRANCH: &str = "feature/test";

    fn setup_config(config_dir_buf: &PathBuf, repository_dir_buf: &PathBuf, worktrees_dir_buf: &PathBuf) {
        TreeflowCommand::new(config_dir_buf.clone())
            .project_add(&repository_dir_buf, Some(&worktrees_dir_buf))
            .current_dir(&repository_dir_buf)
            .cmd()
            .assert()
            .success();
    }

    #[test]
    fn finish_from_main_working_tree_fails() {
        let config_dir = TempDir::new("config_dir").expect("should be able to create temp config dir");
        let repository_dir = TempDir::new("repository_dir").expect("should be able to create temp repository dir");
        let worktrees_dir = TempDir::new("worktrees_dir").expect("should be able to create temp worktrees dir");

        let config_dir_buf = config_dir.path().to_path_buf();
        let repository_dir_buf = repository_dir.path().to_path_buf();
        let worktrees_dir_buf = worktrees_dir.path().to_path_buf();

        setup_config(&config_dir_buf, &repository_dir_buf, &worktrees_dir_buf);
        GitCommand::init(&repository_dir_buf);

        // Run finish command from repository directory
        TreeflowCommand::new(config_dir_buf)
            .current_dir(&repository_dir_buf)
            .cmd()
            .arg("finish")
            .assert()
            .failure()
            .stderr(predicates::str::contains("main working tree"));
    }

    #[test]
    fn removes_worktree_and_navigates_to_primary() {
        let config_dir = TempDir::new("config_dir").expect("should be able to create temp config dir");
        let repository_dir = TempDir::new("repository_dir").expect("should be able to create temp repository dir");
        let worktrees_dir = TempDir::new("worktrees_dir").expect("should be able to create temp worktrees dir");

        let config_dir_buf = config_dir.path().to_path_buf();
        let repository_dir_buf = repository_dir.path().to_path_buf();
        let worktrees_dir_buf = worktrees_dir.path().to_path_buf();

        let worktree_dir = worktrees_dir_buf.join(TEST_BRANCH);

        setup_config(&config_dir_buf, &repository_dir_buf, &worktrees_dir_buf);
        GitCommand::init(&repository_dir_buf);

        // Create the actual worktree directory inside the worktrees parent directory
        GitCommand::create_worktree(&repository_dir_buf, TEST_BRANCH, &worktree_dir);

        // Verify worktree directory exists
        assert!(worktree_dir.exists(), "Worktree directory should exist before finish");

        // Run finish command
        TreeflowCommand::new(config_dir_buf)
            .current_dir(&worktree_dir)
            .cmd()
            .arg("finish")
            .assert()
            .success()
            .stdout(Return::Cd { path: repository_dir_buf.clone() }.print());

        assert!(!GitCommand::worktree_exists(&repository_dir_buf, &worktree_dir), "Worktree should be removed after finish");
        assert!(!worktree_dir.exists(), "Worktree directory should be removed after finish");
        assert!(!GitCommand::branch_exists(&repository_dir_buf, TEST_BRANCH), "Branch should be deleted after finish");
    }

    #[test]
    fn force_removes_worktree_and_navigates_to_primary() {
        let config_dir = TempDir::new("config_dir").expect("should be able to create temp config dir");
        let repository_dir = TempDir::new("repository_dir").expect("should be able to create temp repository dir");
        let worktrees_dir = TempDir::new("worktrees_dir").expect("should be able to create temp worktrees dir");

        let config_dir_buf = config_dir.path().to_path_buf();
        let repository_dir_buf = repository_dir.path().to_path_buf();
        let worktrees_dir_buf = worktrees_dir.path().to_path_buf();

        let worktree_dir = worktrees_dir_buf.join(TEST_BRANCH);

        setup_config(&config_dir_buf, &repository_dir_buf, &worktrees_dir_buf);
        GitCommand::init(&repository_dir_buf);

        // Create the actual worktree directory inside the worktrees parent directory
        GitCommand::create_worktree(&repository_dir_buf, TEST_BRANCH, &worktree_dir);

        // Create an untracked file in the worktree directory
        fs::write(worktree_dir.join("untracked_file.txt"), "This file is not tracked by Git, requires force to remove").expect("Failed to create untracked file");

        // Run finish command
        TreeflowCommand::new(config_dir_buf)
            .current_dir(&worktree_dir)
            .cmd()
            .args(["finish", "--force"])
            .assert()
            .success()
            .stdout(Return::Cd { path: repository_dir_buf.clone() }.print());

        assert!(!GitCommand::worktree_exists(&repository_dir_buf, &worktree_dir), "Worktree should be removed after finish");
        assert!(!worktree_dir.exists(), "Worktree directory should be removed after finish");
        assert!(!GitCommand::branch_exists(&repository_dir_buf, TEST_BRANCH), "Branch should be deleted after finish");
    }

    #[test]
    fn untracked_changes_without_force_fails() {
        let config_dir = TempDir::new("config_dir").expect("should be able to create temp config dir");
        let repository_dir = TempDir::new("repository_dir").expect("should be able to create temp repository dir");
        let worktrees_dir = TempDir::new("worktrees_dir").expect("should be able to create temp worktrees dir");

        let config_dir_buf = config_dir.path().to_path_buf();
        let repository_dir_buf = repository_dir.path().to_path_buf();
        let worktrees_dir_buf = worktrees_dir.path().to_path_buf();

        let worktree_dir = worktrees_dir_buf.join(TEST_BRANCH);

        setup_config(&config_dir_buf, &repository_dir_buf, &worktrees_dir_buf);
        GitCommand::init(&repository_dir_buf);

        // Create the actual worktree directory inside the worktrees parent directory
        GitCommand::create_worktree(&repository_dir_buf, TEST_BRANCH, &worktree_dir);

        // Create an untracked file in the worktree directory
        fs::write(worktree_dir.join("untracked_file.txt"), "This file is not tracked by Git, requires force to remove").expect("Failed to create untracked file");

        // Run finish command
        TreeflowCommand::new(config_dir_buf)
            .current_dir(&worktree_dir)
            .cmd()
            .arg("finish")
            .assert()
            .failure();

        assert!(GitCommand::worktree_exists(&repository_dir_buf, &worktree_dir), "Worktree should exist still after failed finish");
        assert!(worktree_dir.exists(), "Worktree directory should not be removed after failed finish");
        assert!(GitCommand::branch_exists(&repository_dir_buf, TEST_BRANCH), "Branch should not be deleted after failed finish");
    }
}