1use std::process::Command;
2
3pub fn run(branch_name: String, from: Option<String>) {
4 if let Err(msg) = validate_branch_name(&branch_name) {
6 eprintln!("{}", format_error_message(msg));
7 return;
8 }
9
10 if branch_exists(&branch_name) {
12 eprintln!("{}", format_branch_exists_message(&branch_name));
13 return;
14 }
15
16 let base_branch = match from {
18 Some(ref branch) => {
19 if !branch_exists(branch) && !is_valid_ref(branch) {
20 eprintln!("{}", format_invalid_base_message(branch));
21 return;
22 }
23 branch.clone()
24 }
25 None => match get_current_branch() {
26 Ok(branch) => branch,
27 Err(msg) => {
28 eprintln!("{}", format_error_message(msg));
29 return;
30 }
31 },
32 };
33
34 println!(
35 "{}",
36 format_creating_branch_message(&branch_name, &base_branch)
37 );
38
39 if let Err(msg) = create_branch(&branch_name, &base_branch) {
41 eprintln!("{}", format_error_message(msg));
42 return;
43 }
44
45 if let Err(msg) = switch_to_branch(&branch_name) {
47 eprintln!("{}", format_error_message(msg));
48 return;
49 }
50
51 println!("{}", format_success_message(&branch_name));
52}
53
54fn validate_branch_name(name: &str) -> Result<(), &'static str> {
56 if name.is_empty() {
57 return Err("Branch name cannot be empty");
58 }
59
60 if name.starts_with('-') {
61 return Err("Branch name cannot start with a dash");
62 }
63
64 if name.contains("..") {
65 return Err("Branch name cannot contain '..'");
66 }
67
68 if name.contains(' ') {
69 return Err("Branch name cannot contain spaces");
70 }
71
72 const INVALID_CHARS: &[char] = &['~', '^', ':', '?', '*', '[', '\\'];
74 if name.chars().any(|c| INVALID_CHARS.contains(&c)) {
75 return Err("Branch name contains invalid characters");
76 }
77
78 Ok(())
79}
80
81fn branch_exists(branch_name: &str) -> bool {
83 Command::new("git")
84 .args([
85 "show-ref",
86 "--verify",
87 "--quiet",
88 &format!("refs/heads/{branch_name}"),
89 ])
90 .status()
91 .map(|status| status.success())
92 .unwrap_or(false)
93}
94
95fn is_valid_ref(ref_name: &str) -> bool {
97 Command::new("git")
98 .args(["rev-parse", "--verify", "--quiet", ref_name])
99 .status()
100 .map(|status| status.success())
101 .unwrap_or(false)
102}
103
104fn get_current_branch() -> Result<String, &'static str> {
106 let output = Command::new("git")
107 .args(["rev-parse", "--abbrev-ref", "HEAD"])
108 .output()
109 .map_err(|_| "Failed to get current branch")?;
110
111 if !output.status.success() {
112 return Err("Not in a git repository");
113 }
114
115 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
116}
117
118fn create_branch(branch_name: &str, base_branch: &str) -> Result<(), &'static str> {
120 let status = Command::new("git")
121 .args(["branch", branch_name, base_branch])
122 .status()
123 .map_err(|_| "Failed to execute git branch command")?;
124
125 if !status.success() {
126 return Err("Failed to create branch");
127 }
128
129 Ok(())
130}
131
132fn switch_to_branch(branch_name: &str) -> Result<(), &'static str> {
134 let status = Command::new("git")
135 .args(["switch", branch_name])
136 .status()
137 .map_err(|_| "Failed to execute git switch command")?;
138
139 if !status.success() {
140 return Err("Failed to switch to new branch");
141 }
142
143 Ok(())
144}
145
146pub fn get_validation_rules() -> &'static [&'static str] {
148 &[
149 "Cannot be empty",
150 "Cannot start with a dash",
151 "Cannot contain '..'",
152 "Cannot contain spaces",
153 "Cannot contain ~^:?*[\\",
154 ]
155}
156
157pub fn format_error_message(msg: &str) -> String {
159 format!("❌ {msg}")
160}
161
162pub fn format_branch_exists_message(branch_name: &str) -> String {
164 format!("❌ Branch '{branch_name}' already exists")
165}
166
167pub fn format_invalid_base_message(base_branch: &str) -> String {
169 format!("❌ Base branch or ref '{base_branch}' does not exist")
170}
171
172pub fn format_creating_branch_message(branch_name: &str, base_branch: &str) -> String {
174 format!("🌿 Creating branch '{branch_name}' from '{base_branch}'...")
175}
176
177pub fn format_success_message(branch_name: &str) -> String {
179 format!("✅ Created and switched to branch '{branch_name}'")
180}
181
182pub fn get_git_branch_args() -> [&'static str; 2] {
184 ["branch", "-"]
185}
186
187pub fn get_git_switch_args() -> [&'static str; 2] {
189 ["switch", "-"]
190}