use std::path::PathBuf;
use crate::config::Config;
use crate::error::{GwmError, Result};
use crate::shell::exec;
use crate::utils::sanitize_branch_name;
use super::core::{get_repository_name, local_branch_exists};
#[derive(Debug, Clone)]
pub struct AddWorktreeOptions {
pub branch: String,
pub is_remote: bool,
pub from_branch: Option<String>,
}
#[derive(Debug)]
pub struct AddWorktreeResult {
pub path: PathBuf,
pub actions: Vec<String>,
}
pub fn add_worktree(config: &Config, options: &AddWorktreeOptions) -> Result<AddWorktreeResult> {
let repo_name = get_repository_name();
let sanitized_branch = sanitize_branch_name(&options.branch);
let base_path = config
.expanded_worktree_base_path()
.ok_or_else(|| GwmError::Path("Failed to expand worktree base path".to_string()))?;
let worktree_path = base_path.join(repo_name).join(sanitized_branch);
let local_exists = local_branch_exists(&options.branch);
let mut actions = Vec::new();
let args: Vec<String> = if local_exists {
actions.push(format!("Using existing local branch: {}", options.branch));
vec![
"worktree".to_string(),
"add".to_string(),
worktree_path.display().to_string(),
options.branch.clone(),
]
} else if options.is_remote {
actions.push(format!(
"Creating worktree from remote branch: origin/{}",
options.branch
));
vec![
"worktree".to_string(),
"add".to_string(),
worktree_path.display().to_string(),
"-b".to_string(),
options.branch.clone(),
format!("origin/{}", options.branch),
]
} else {
let base = options.from_branch.as_deref().unwrap_or_else(|| {
config
.main_branches
.first()
.map(|s| s.as_str())
.unwrap_or("main")
});
actions.push(format!(
"Creating new branch '{}' from '{}'",
options.branch, base
));
vec![
"worktree".to_string(),
"add".to_string(),
worktree_path.display().to_string(),
"-b".to_string(),
options.branch.clone(),
base.to_string(),
]
};
let args_ref: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
exec("git", &args_ref, None)?;
actions.push(format!("Worktree created at: {}", worktree_path.display()));
Ok(AddWorktreeResult {
path: worktree_path,
actions,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_worktree_options_construction() {
let options = AddWorktreeOptions {
branch: "feature/test".to_string(),
is_remote: false,
from_branch: Some("develop".to_string()),
};
assert_eq!(options.branch, "feature/test");
assert!(!options.is_remote);
assert_eq!(options.from_branch, Some("develop".to_string()));
}
#[test]
fn test_add_worktree_options_remote() {
let options = AddWorktreeOptions {
branch: "feature/remote-branch".to_string(),
is_remote: true,
from_branch: None,
};
assert!(options.is_remote);
assert!(options.from_branch.is_none());
}
#[test]
fn test_add_worktree_options_default_from_branch() {
let options = AddWorktreeOptions {
branch: "feature/new".to_string(),
is_remote: false,
from_branch: None,
};
assert!(options.from_branch.is_none());
}
#[test]
fn test_add_worktree_result_construction() {
let result = AddWorktreeResult {
path: PathBuf::from("/path/to/worktree"),
actions: vec![
"Creating new branch".to_string(),
"Worktree created".to_string(),
],
};
assert_eq!(result.path, PathBuf::from("/path/to/worktree"));
assert_eq!(result.actions.len(), 2);
}
#[test]
fn test_add_worktree_result_empty_actions() {
let result = AddWorktreeResult {
path: PathBuf::from("/path"),
actions: vec![],
};
assert!(result.actions.is_empty());
}
#[test]
fn test_add_worktree_options_branch_with_slashes() {
let options = AddWorktreeOptions {
branch: "feature/sub/deep/branch".to_string(),
is_remote: false,
from_branch: Some("develop".to_string()),
};
assert!(options.branch.contains('/'));
}
}