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;
#[allow(clippy::missing_panics_doc)]
pub fn cmd_create(
branch: &str,
start_point: Option<&str>,
color_mode: color::ColorMode,
) -> Result<()> {
let repo_root = get_main_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")?;
#[allow(clippy::literal_string_with_formatting_args)]
let path_template = config
.worktree
.dir
.replace("{repo}", repo_name)
.replace("{branch}", branch);
let worktree_path = if path_template.starts_with('/') {
std::path::PathBuf::from(&path_template)
} else {
repo_root.join(&path_template)
};
let mut cmd = Command::new("git");
if let Some(sp) = start_point {
cmd.args(["worktree", "add", "-b", branch])
.arg(&worktree_path)
.arg(sp);
} else {
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 {
cmd.args(["worktree", "add"])
.arg(&worktree_path)
.arg(branch);
} else {
cmd.args(["worktree", "add", "-b", branch])
.arg(&worktree_path);
}
}
let mp = MultiProgress::new();
let is_tty = color_mode.should_colorize();
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}");
}
let created_msg = format!("Created worktree at: {}", display_path(&worktree_path));
if !is_tty {
eprintln!("{}", color::success(color_mode, &created_msg));
}
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,
);
}
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)));
}
integrations::zoxide::add_to_zoxide_if_enabled(
&worktree_path,
config.integrations.zoxide.enabled,
)?;
Ok(())
}