ofsht 0.6.0

Git worktree management tool
Documentation
//! Create command - Simple worktree creation without extra features

use anyhow::{Context, Result};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use std::process::Command;
use std::time::Duration;

use crate::color;
use crate::commands::common::get_main_repo_root;
use crate::config;
use crate::domain::worktree::display_path;
use crate::hooks;
use crate::integrations;

/// Create a new worktree (simple version without tmux/GitHub integration)
///
/// # Errors
/// Returns an error if:
/// - Not in a git repository
/// - Git worktree creation fails
/// - Zoxide registration fails
#[allow(clippy::missing_panics_doc)]
pub fn cmd_create(
    branch: &str,
    start_point: Option<&str>,
    color_mode: color::ColorMode,
) -> Result<()> {
    // Get main repository root
    let repo_root = get_main_repo_root()?;

    // Load configuration from repo root
    let config = config::Config::load_from_repo_root(&repo_root)?;
    let repo_name = repo_root
        .file_name()
        .and_then(|n| n.to_str())
        .context("Failed to get repository name")?;

    // Expand path template
    #[allow(clippy::literal_string_with_formatting_args)]
    let path_template = config
        .worktree
        .dir
        .replace("{repo}", repo_name)
        .replace("{branch}", branch);

    // Create worktree path (relative paths are resolved from repo root)
    let worktree_path = if path_template.starts_with('/') {
        std::path::PathBuf::from(&path_template)
    } else {
        repo_root.join(&path_template)
    };

    // Create worktree using git worktree add
    let mut cmd = Command::new("git");

    if let Some(sp) = start_point {
        // Create new branch with start point
        cmd.args(["worktree", "add", "-b", branch])
            .arg(&worktree_path)
            .arg(sp);
    } else {
        // No start_point: try to checkout existing branch, or create from HEAD
        // Check if branch exists
        let branch_exists = Command::new("git")
            .args(["rev-parse", "--verify", branch])
            .current_dir(&repo_root)
            .output()
            .is_ok_and(|o| o.status.success());

        if branch_exists {
            // Checkout existing branch (no -b flag)
            cmd.args(["worktree", "add"])
                .arg(&worktree_path)
                .arg(branch);
        } else {
            // Create new branch from HEAD
            cmd.args(["worktree", "add", "-b", branch])
                .arg(&worktree_path);
        }
    }

    let mp = MultiProgress::new();
    let is_tty = color_mode.should_colorize();

    // Header spinner (TTY) or deferred header (non-TTY)
    let header_pb = if is_tty {
        let pb = mp.add(ProgressBar::new_spinner());
        pb.set_style(
            ProgressStyle::default_spinner()
                .template("{spinner:.cyan} {msg}")
                .unwrap(),
        );
        pb.set_message(format!("Creating {branch}"));
        pb.enable_steady_tick(Duration::from_millis(100));
        Some(pb)
    } else {
        None
    };

    let output = cmd.output().context("Failed to execute git worktree add")?;

    if !output.status.success() {
        if let Some(pb) = header_pb {
            pb.finish_and_clear();
        }
        let stderr = String::from_utf8_lossy(&output.stderr);
        anyhow::bail!("git worktree add failed: {stderr}");
    }

    // non-TTY: print header before hooks (rm/sync pattern)
    let created_msg = format!("Created worktree at: {}", display_path(&worktree_path));
    if !is_tty {
        eprintln!("{}", color::success(color_mode, &created_msg));
    }

    // Execute create hooks
    if !config.hooks.create.run.is_empty()
        || !config.hooks.create.copy.is_empty()
        || !config.hooks.create.link.is_empty()
    {
        hooks::execute_hooks_lenient_with_mp(
            &config.hooks.create,
            &worktree_path,
            &repo_root,
            color_mode,
            "  ",
            &mp,
        );
    }

    // Finish header: Creating → Created
    if let Some(pb) = header_pb {
        pb.set_style(ProgressStyle::with_template("{msg}").unwrap());
        pb.finish_with_message(format!("{}", color::success(color_mode, created_msg)));
    }

    // Add to zoxide if enabled
    integrations::zoxide::add_to_zoxide_if_enabled(
        &worktree_path,
        config.integrations.zoxide.enabled,
    )?;

    Ok(())
}