1use crate::core::git::GitOperations;
2use crate::{GitXError, Result};
3use std::process::Command;
4
5pub fn run(target: Option<String>) -> Result<String> {
6 let target_branch = target.unwrap_or_else(get_default_target);
7
8 let current_branch = GitOperations::current_branch()
10 .map_err(|e| GitXError::GitCommand(format!("Failed to get current branch: {e}")))?;
11
12 let mut output = Vec::new();
13 output.push(format_branch_comparison(¤t_branch, &target_branch));
14
15 let rev_list_output = Command::new("git")
17 .args([
18 "rev-list",
19 "--left-right",
20 "--count",
21 &format_rev_list_range(&target_branch, ¤t_branch),
22 ])
23 .output()
24 .map_err(|_| GitXError::GitCommand("Failed to get ahead/behind count".to_string()))?;
25
26 if !rev_list_output.status.success() {
27 return Err(GitXError::GitCommand(
28 "Failed to compare branches".to_string(),
29 ));
30 }
31
32 let output_str = String::from_utf8_lossy(&rev_list_output.stdout);
33 let (ahead, behind) = parse_commit_counts(&output_str);
34
35 let (ahead_msg, behind_msg) = format_commit_counts(&ahead, &behind);
36 output.push(ahead_msg);
37 output.push(behind_msg);
38
39 let diff_output = Command::new("git")
41 .args([
42 "diff",
43 "--name-status",
44 &format_rev_list_range(&target_branch, ¤t_branch),
45 ])
46 .output()
47 .map_err(|_| GitXError::GitCommand("Failed to get diff".to_string()))?;
48
49 if !diff_output.status.success() {
50 return Err(GitXError::GitCommand(
51 "Failed to get file changes".to_string(),
52 ));
53 }
54
55 let diff = String::from_utf8_lossy(&diff_output.stdout);
56
57 output.push("Changes:".to_string());
58 for line in diff.lines() {
59 if let Some(formatted_line) = format_diff_line(line) {
60 output.push(formatted_line);
61 }
62 }
63
64 Ok(output.join("\n"))
65}
66
67const DEFAULT_TARGET: &str = "main";
68
69pub fn get_default_target() -> String {
70 DEFAULT_TARGET.to_string()
71}
72
73pub fn format_branch_comparison(current: &str, target: &str) -> String {
74 format!("Branch: {current} vs {target}")
75}
76
77pub fn format_commit_counts(ahead: &str, behind: &str) -> (String, String) {
78 (
79 format!("+ {ahead} commits ahead"),
80 format!("- {behind} commits behind"),
81 )
82}
83
84pub fn format_rev_list_range(target: &str, current: &str) -> String {
85 format!("{target}...{current}")
86}
87
88pub fn parse_commit_counts(output: &str) -> (String, String) {
89 let mut counts = output.split_whitespace();
90 let behind = counts.next().unwrap_or("0").to_string();
91 let ahead = counts.next().unwrap_or("0").to_string();
92 (ahead, behind)
93}
94
95pub fn git_status_to_symbol(status: &str) -> &str {
96 match status {
97 "A" => "+",
98 "M" => "~",
99 "D" => "-",
100 other => other,
101 }
102}
103
104pub fn format_diff_line(line: &str) -> Option<String> {
105 let parts: Vec<&str> = line.split_whitespace().collect();
106 if parts.len() >= 2 {
107 let symbol = git_status_to_symbol(parts[0]);
108 Some(format!(" - {} {}", symbol, parts[1]))
109 } else {
110 None
111 }
112}