pub mod add;
pub mod args;
pub mod capture;
pub mod changelog;
pub mod clipboard_read;
pub mod close;
pub mod config;
pub mod dashboard;
pub mod docs;
pub mod exec;
pub mod host_exec;
pub mod last_agent;
pub mod last_done;
pub mod list;
pub mod merge;
pub mod open;
pub mod path;
pub mod remove;
pub mod resurrect;
pub mod run;
pub mod sandbox;
pub mod sandbox_run;
pub mod send;
pub mod set_base;
pub mod set_window_status;
pub mod setup;
pub mod sidebar;
pub mod status;
pub mod sync_files;
pub mod update;
pub mod wait;
use anyhow::{Context, Result, anyhow};
use crate::{config::Config, workflow::SetupOptions};
pub enum HookPhase {
PostCreate,
PreMerge,
PreRemove,
}
pub fn announce_hooks(config: &Config, options: Option<&SetupOptions>, phase: HookPhase) -> bool {
match phase {
HookPhase::PostCreate => {
let should_run = options.is_some_and(|opts| opts.run_hooks)
&& config.post_create.as_ref().is_some_and(|v| !v.is_empty());
if should_run {
println!("Running setup commands...");
}
should_run
}
HookPhase::PreMerge => {
let should_run = config.pre_merge.as_ref().is_some_and(|v| !v.is_empty());
if should_run {
println!("Running pre-merge commands...");
}
should_run
}
HookPhase::PreRemove => {
let should_run = config.pre_remove.as_ref().is_some_and(|v| !v.is_empty());
if should_run {
println!("Running pre-remove commands...");
}
should_run
}
}
}
pub fn resolve_name(arg: Option<&str>) -> Result<String> {
match arg {
Some(name) => Ok(name.to_string()),
None => {
let cwd = std::env::current_dir().context("Failed to get current directory")?;
resolve_name_from_path(&cwd)
}
}
}
fn resolve_name_from_path(path: &std::path::Path) -> Result<String> {
let mut iter = path.components().rev();
let mut child_name: Option<String> = iter
.next()
.and_then(|c| c.as_os_str().to_str())
.map(|s| s.to_string());
for component in iter {
let name = component.as_os_str().to_str().unwrap_or("");
if name.ends_with("__worktrees") {
return child_name
.ok_or_else(|| anyhow!("Found worktree container but no child directory"));
}
child_name = Some(name.to_string());
}
path.file_name()
.and_then(|n| n.to_str())
.map(|s| s.to_string())
.ok_or_else(|| anyhow!("Could not determine worktree name from current directory"))
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn test_resolve_name_with_explicit_arg() {
assert_eq!(resolve_name(Some("my-feature")).unwrap(), "my-feature");
}
#[test]
fn test_resolve_name_from_worktree_root() {
let path: PathBuf = ["/", "home", "user", "project__worktrees", "my-feature"]
.iter()
.collect();
assert_eq!(resolve_name_from_path(&path).unwrap(), "my-feature");
}
#[test]
fn test_resolve_name_from_subdirectory() {
let path: PathBuf = [
"/",
"home",
"user",
"project__worktrees",
"my-feature",
"src",
"components",
]
.iter()
.collect();
assert_eq!(resolve_name_from_path(&path).unwrap(), "my-feature");
}
#[test]
fn test_resolve_name_from_non_worktree_directory() {
let path: PathBuf = ["/", "home", "user", "some-project"].iter().collect();
assert_eq!(resolve_name_from_path(&path).unwrap(), "some-project");
}
#[test]
fn test_resolve_name_from_deeply_nested_subdirectory() {
let path: PathBuf = [
"/",
"home",
"user",
"project__worktrees",
"fix-bug",
"src",
"lib",
"utils",
"helpers",
]
.iter()
.collect();
assert_eq!(resolve_name_from_path(&path).unwrap(), "fix-bug");
}
#[test]
fn test_resolve_name_with_nested_worktrees_dirs() {
let path: PathBuf = [
"/",
"backup__worktrees",
"project__worktrees",
"feature",
"src",
]
.iter()
.collect();
assert_eq!(resolve_name_from_path(&path).unwrap(), "feature");
}
}