1use crate::core::interactive::Interactive;
2use crate::{GitXError, Result};
3use console::style;
4use std::process::Command;
5
6pub fn run() -> Result<String> {
7 let branches = get_recent_branches()?;
8
9 if branches.is_empty() {
10 return Err(GitXError::GitCommand(
11 "No recent branches found".to_string(),
12 ));
13 }
14
15 if !Interactive::is_interactive() {
17 let selected_branch = &branches[0];
19 switch_to_branch(selected_branch)?;
20 return Ok(format!(
21 "Switched to branch '{}'",
22 style(selected_branch).green().bold()
23 ));
24 }
25
26 let selected_branch =
27 Interactive::branch_picker(&branches, Some("Select a recent branch to switch to"))?;
28 switch_to_branch(&selected_branch)?;
29
30 Ok(format!(
31 "Switched to branch '{}'",
32 style(&selected_branch).green().bold()
33 ))
34}
35
36fn get_recent_branches() -> Result<Vec<String>> {
37 let output = Command::new("git")
38 .args([
39 "for-each-ref",
40 "--sort=-committerdate",
41 "--format=%(refname:short)",
42 "refs/heads/",
43 ])
44 .output()?;
45
46 if !output.status.success() {
47 return Err(GitXError::GitCommand(format!(
48 "Failed to get recent branches: {}",
49 String::from_utf8_lossy(&output.stderr)
50 )));
51 }
52
53 let current_branch = get_current_branch().unwrap_or_default();
54 let branches: Vec<String> = String::from_utf8_lossy(&output.stdout)
55 .lines()
56 .map(|s| s.trim().to_string())
57 .filter(|branch| !branch.is_empty() && branch != ¤t_branch)
58 .take(10) .collect();
60
61 Ok(branches)
62}
63
64fn get_current_branch() -> Result<String> {
65 let output = Command::new("git")
66 .args(["branch", "--show-current"])
67 .output()?;
68
69 if !output.status.success() {
70 return Err(GitXError::GitCommand(format!(
71 "Failed to get current branch: {}",
72 String::from_utf8_lossy(&output.stderr)
73 )));
74 }
75
76 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
77}
78
79fn switch_to_branch(branch: &str) -> Result<()> {
80 let status = Command::new("git").args(["checkout", branch]).status()?;
81
82 if !status.success() {
83 return Err(GitXError::GitCommand(format!(
84 "Failed to switch to branch '{branch}'"
85 )));
86 }
87
88 Ok(())
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use crate::GitXError;
95
96 #[test]
97 fn test_get_recent_branches_success() {
98 match get_recent_branches() {
99 Ok(branches) => {
100 assert!(branches.len() <= 10, "Should limit to 10 branches");
101 for branch in branches {
102 assert!(!branch.is_empty(), "Branch names should not be empty");
103 }
104 }
105 Err(GitXError::GitCommand(_)) => {
106 }
108 Err(GitXError::Io(_)) => {
109 }
111 Err(_) => panic!("Unexpected error type"),
112 }
113 }
114
115 #[test]
116 fn test_get_current_branch_success() {
117 match get_current_branch() {
118 Ok(_branch) => {
119 }
122 Err(GitXError::GitCommand(_)) => {
123 }
125 Err(GitXError::Io(_)) => {
126 }
128 Err(_) => panic!("Unexpected error type"),
129 }
130 }
131
132 #[test]
133 fn test_switch_to_branch_invalid_branch() {
134 let result = switch_to_branch("non-existent-branch-12345");
135 assert!(result.is_err(), "Should fail for non-existent branch");
136
137 if let Err(e) = result {
138 match e {
139 GitXError::GitCommand(msg) => {
140 assert!(
141 msg.contains("Failed to switch to branch"),
142 "Error message should mention branch switching"
143 );
144 }
145 GitXError::Io(_) => {
146 }
148 _ => panic!("Unexpected error type"),
149 }
150 }
151 }
152
153 #[test]
154 fn test_run_no_git_repo() {
155 let result = get_recent_branches();
158 match result {
159 Ok(branches) => {
160 assert!(branches.len() <= 10, "Should limit to 10 branches");
162 for branch in branches {
163 assert!(!branch.is_empty(), "Branch names should not be empty");
164 }
165 println!("In git repo - skipping non-git test");
166 }
167 Err(GitXError::GitCommand(_)) => {
168 println!("Not in git repo - git command failed as expected");
170 }
171 Err(GitXError::Io(_)) => {
172 println!("Git binary not available - IO error as expected");
174 }
175 Err(_) => panic!("Unexpected error type"),
176 }
177 }
178
179 #[test]
180 fn test_show_branch_picker_with_branches() {
181 let branches = ["feature/test".to_string(), "main".to_string()];
182
183 assert!(!branches.is_empty(), "Should have branches to pick from");
189 assert_eq!(branches.len(), 2, "Should have exactly 2 branches");
190 assert_eq!(
191 branches[0], "feature/test",
192 "First branch should be feature/test"
193 );
194 assert_eq!(branches[1], "main", "Second branch should be main");
195
196 }
199
200 #[test]
201 fn test_show_branch_picker_empty_branches() {
202 let branches: Vec<String> = vec![];
203
204 assert!(branches.is_empty(), "Should have no branches");
206 assert_eq!(branches.len(), 0, "Should have exactly 0 branches");
207
208 }
212
213 #[test]
214 fn test_switch_to_branch_valid_args() {
215 let result = switch_to_branch("main");
218 match result {
219 Ok(_) => {
220 }
222 Err(GitXError::GitCommand(msg)) => {
223 assert!(
225 msg.contains("Failed to switch to branch"),
226 "Should mention switching failure"
227 );
228 }
229 Err(GitXError::Io(_)) => {
230 }
232 Err(_) => panic!("Unexpected error type"),
233 }
234 }
235
236 #[test]
237 fn test_gitx_error_types() {
238 let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "git not found");
242 let gitx_error: GitXError = io_error.into();
243 match gitx_error {
244 GitXError::Io(_) => {} _ => panic!("Should convert to Io error"),
246 }
247
248 let git_error = GitXError::GitCommand("test error".to_string());
250 assert_eq!(git_error.to_string(), "Git command failed: test error");
251 }
252
253 #[test]
254 fn test_run_function_complete_flow() {
255 let branches_result = get_recent_branches();
260 match branches_result {
261 Ok(branches) => {
262 assert!(branches.len() <= 10, "Should limit to 10 branches");
264 for branch in &branches {
265 assert!(!branch.is_empty(), "Branch names should not be empty");
266 }
267
268 if branches.is_empty() {
271 println!(
272 "No recent branches found - this would trigger the empty branches error"
273 );
274 } else {
275 println!(
276 "Found {} recent branches - skipping interactive test to avoid hanging",
277 branches.len()
278 );
279 }
280 }
281 Err(_) => {
282 println!("Failed to get recent branches - this would trigger the git error");
284 }
285 }
286 }
287}