git_x/
what.rs

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    // Get current branch name
9    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(&current_branch, &target_branch));
14
15    // Get ahead/behind commit counts
16    let rev_list_output = Command::new("git")
17        .args([
18            "rev-list",
19            "--left-right",
20            "--count",
21            &format_rev_list_range(&target_branch, &current_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    // Get diff summary
40    let diff_output = Command::new("git")
41        .args([
42            "diff",
43            "--name-status",
44            &format_rev_list_range(&target_branch, &current_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}