use crate::config::Config;
use crate::git::get_repository_name;
pub fn validate_branch_name(name: &str) -> Option<String> {
let name = name.trim();
if name.is_empty() {
return Some("Branch name cannot be empty".to_string());
}
let invalid_chars = ['~', '^', ':', '?', '*', '[', ']', '\\', '@'];
for c in invalid_chars {
if name.contains(c) {
return Some(format!("Branch name contains invalid character: {}", c));
}
}
if name.starts_with('.') || name.ends_with('.') {
return Some("Branch name cannot start or end with a dot".to_string());
}
if name.contains("..") {
return Some("Branch name cannot contain consecutive dots".to_string());
}
if name.contains(' ') {
return Some("Branch name cannot contain spaces".to_string());
}
if name.len() > 50 {
return Some("Branch name is too long (max 50 characters)".to_string());
}
if name.ends_with(".lock") {
return Some("Branch name cannot end with '.lock'".to_string());
}
if name.contains("//") {
return Some("Branch name cannot contain consecutive slashes".to_string());
}
if name.ends_with('/') {
return Some("Branch name cannot end with '/'".to_string());
}
if name.starts_with('/') {
return Some("Branch name cannot start with '/'".to_string());
}
None
}
pub fn sanitize_branch_name(branch: &str) -> String {
branch.replace('/', "-")
}
pub fn generate_worktree_preview(branch_name: &str, config: &Config) -> Option<String> {
let branch_name = branch_name.trim();
if branch_name.is_empty() {
return None;
}
let repo_name = get_repository_name();
let sanitized_branch = sanitize_branch_name(branch_name);
let base_path = config
.expanded_worktree_base_path()
.map(|p| p.display().to_string())
.unwrap_or_else(|| config.worktree_base_path.clone());
Some(format!("{}/{}/{}", base_path, repo_name, sanitized_branch))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_branch_name_valid() {
assert!(validate_branch_name("feature/test").is_none());
assert!(validate_branch_name("fix-bug-123").is_none());
assert!(validate_branch_name("v1.0.0").is_none());
assert!(validate_branch_name("main").is_none());
assert!(validate_branch_name("feature/user/auth").is_none());
}
#[test]
fn test_validate_branch_name_empty() {
assert!(validate_branch_name("").is_some());
assert!(validate_branch_name(" ").is_some());
}
#[test]
fn test_validate_branch_name_invalid_chars() {
assert!(validate_branch_name("feature~test").is_some());
assert!(validate_branch_name("branch^name").is_some());
assert!(validate_branch_name("test:name").is_some());
assert!(validate_branch_name("test?name").is_some());
assert!(validate_branch_name("test*name").is_some());
assert!(validate_branch_name("test[name").is_some());
assert!(validate_branch_name("test]name").is_some());
assert!(validate_branch_name("test\\name").is_some());
assert!(validate_branch_name("test@name").is_some());
}
#[test]
fn test_validate_branch_name_dots() {
assert!(validate_branch_name(".hidden").is_some());
assert!(validate_branch_name("hidden.").is_some());
assert!(validate_branch_name("branch..name").is_some());
}
#[test]
fn test_validate_branch_name_spaces() {
assert!(validate_branch_name("with space").is_some());
assert!(validate_branch_name("hello world").is_some());
}
#[test]
fn test_validate_branch_name_too_long() {
let long_name = "a".repeat(51);
assert!(validate_branch_name(&long_name).is_some());
let max_name = "a".repeat(50);
assert!(validate_branch_name(&max_name).is_none());
}
#[test]
fn test_validate_branch_name_lock_suffix() {
assert!(validate_branch_name("branch.lock").is_some());
assert!(validate_branch_name("feature/test.lock").is_some());
assert!(validate_branch_name("test.lockfile").is_none());
}
#[test]
fn test_validate_branch_name_slashes() {
assert!(validate_branch_name("feature//test").is_some());
assert!(validate_branch_name("feature/test/").is_some());
assert!(validate_branch_name("/feature/test").is_some());
assert!(validate_branch_name("feature/test").is_none());
assert!(validate_branch_name("feature/user/auth").is_none());
}
#[test]
fn test_sanitize_branch_name() {
assert_eq!(sanitize_branch_name("feature/test"), "feature-test");
assert_eq!(sanitize_branch_name("fix/bug/123"), "fix-bug-123");
assert_eq!(sanitize_branch_name("main"), "main");
assert_eq!(sanitize_branch_name("no-slash"), "no-slash");
}
}