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