git_x/
sync.rs

1use std::process::Command;
2
3pub fn run(merge: bool) {
4    // Get current branch
5    let current_branch = match get_current_branch() {
6        Ok(branch) => branch,
7        Err(msg) => {
8            eprintln!("{}", format_error_message(msg));
9            return;
10        }
11    };
12
13    // Get upstream branch
14    let upstream = match get_upstream_branch(&current_branch) {
15        Ok(upstream) => upstream,
16        Err(msg) => {
17            eprintln!("{}", format_error_message(msg));
18            return;
19        }
20    };
21
22    println!("{}", format_sync_start_message(&current_branch, &upstream));
23
24    // Fetch from remote
25    if let Err(msg) = fetch_upstream(&upstream) {
26        eprintln!("{}", format_error_message(msg));
27        return;
28    }
29
30    // Check if we're ahead of upstream
31    let status = match get_sync_status(&current_branch, &upstream) {
32        Ok(status) => status,
33        Err(msg) => {
34            eprintln!("{}", format_error_message(msg));
35            return;
36        }
37    };
38
39    match status {
40        SyncStatus::UpToDate => {
41            println!("{}", format_up_to_date_message());
42        }
43        SyncStatus::Behind(count) => {
44            println!("{}", format_behind_message(count));
45            if let Err(msg) = sync_with_upstream(&upstream, merge) {
46                eprintln!("{}", format_error_message(msg));
47            } else {
48                println!("{}", format_sync_success_message(merge));
49            }
50        }
51        SyncStatus::Ahead(count) => {
52            println!("{}", format_ahead_message(count));
53        }
54        SyncStatus::Diverged(behind, ahead) => {
55            println!("{}", format_diverged_message(behind, ahead));
56            if merge {
57                if let Err(msg) = sync_with_upstream(&upstream, merge) {
58                    eprintln!("{}", format_error_message(msg));
59                } else {
60                    println!("{}", format_sync_success_message(merge));
61                }
62            } else {
63                println!("{}", format_diverged_help_message());
64            }
65        }
66    }
67}
68
69#[derive(Debug, PartialEq)]
70enum SyncStatus {
71    UpToDate,
72    Behind(u32),
73    Ahead(u32),
74    Diverged(u32, u32), // behind, ahead
75}
76
77// Helper function to get current branch
78fn get_current_branch() -> Result<String, &'static str> {
79    let output = Command::new("git")
80        .args(["rev-parse", "--abbrev-ref", "HEAD"])
81        .output()
82        .map_err(|_| "Failed to get current branch")?;
83
84    if !output.status.success() {
85        return Err("Not in a git repository");
86    }
87
88    Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
89}
90
91// Helper function to get upstream branch
92fn get_upstream_branch(branch: &str) -> Result<String, &'static str> {
93    let output = Command::new("git")
94        .args(["rev-parse", "--abbrev-ref", &format!("{branch}@{{u}}")])
95        .output()
96        .map_err(|_| "Failed to get upstream branch")?;
97
98    if !output.status.success() {
99        return Err("No upstream branch configured");
100    }
101
102    Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
103}
104
105// Helper function to fetch from upstream
106fn fetch_upstream(upstream: &str) -> Result<(), &'static str> {
107    let remote = upstream.split('/').next().unwrap_or("origin");
108
109    let status = Command::new("git")
110        .args(["fetch", remote])
111        .status()
112        .map_err(|_| "Failed to execute fetch command")?;
113
114    if !status.success() {
115        return Err("Failed to fetch from remote");
116    }
117
118    Ok(())
119}
120
121// Helper function to get sync status
122fn get_sync_status(branch: &str, upstream: &str) -> Result<SyncStatus, &'static str> {
123    let output = Command::new("git")
124        .args([
125            "rev-list",
126            "--left-right",
127            "--count",
128            &format!("{upstream}...{branch}"),
129        ])
130        .output()
131        .map_err(|_| "Failed to get sync status")?;
132
133    if !output.status.success() {
134        return Err("Failed to compare with upstream");
135    }
136
137    let counts = String::from_utf8_lossy(&output.stdout);
138    let (behind, ahead) = parse_sync_counts(&counts)?;
139
140    Ok(match (behind, ahead) {
141        (0, 0) => SyncStatus::UpToDate,
142        (b, 0) if b > 0 => SyncStatus::Behind(b),
143        (0, a) if a > 0 => SyncStatus::Ahead(a),
144        (b, a) if b > 0 && a > 0 => SyncStatus::Diverged(b, a),
145        _ => SyncStatus::UpToDate,
146    })
147}
148
149// Helper function to parse sync counts
150pub fn parse_sync_counts(output: &str) -> Result<(u32, u32), &'static str> {
151    let mut parts = output.split_whitespace();
152    let behind = parts
153        .next()
154        .and_then(|s| s.parse().ok())
155        .ok_or("Invalid sync count format")?;
156    let ahead = parts
157        .next()
158        .and_then(|s| s.parse().ok())
159        .ok_or("Invalid sync count format")?;
160
161    Ok((behind, ahead))
162}
163
164// Helper function to sync with upstream
165fn sync_with_upstream(upstream: &str, merge: bool) -> Result<(), &'static str> {
166    let args = if merge {
167        ["merge", upstream]
168    } else {
169        ["rebase", upstream]
170    };
171
172    let status = Command::new("git")
173        .args(args)
174        .status()
175        .map_err(|_| "Failed to execute sync command")?;
176
177    if !status.success() {
178        return Err(if merge {
179            "Merge failed"
180        } else {
181            "Rebase failed"
182        });
183    }
184
185    Ok(())
186}
187
188// Helper function to format sync start message
189pub fn format_sync_start_message(branch: &str, upstream: &str) -> String {
190    format!("🔄 Syncing branch '{branch}' with '{upstream}'...")
191}
192
193// Helper function to format error message
194pub fn format_error_message(msg: &str) -> String {
195    format!("❌ {msg}")
196}
197
198// Helper function to format up to date message
199pub fn format_up_to_date_message() -> &'static str {
200    "✅ Branch is up to date with upstream"
201}
202
203// Helper function to format behind message
204pub fn format_behind_message(count: u32) -> String {
205    format!("⬇️ Branch is {count} commit(s) behind upstream")
206}
207
208// Helper function to format ahead message
209pub fn format_ahead_message(count: u32) -> String {
210    format!("⬆️ Branch is {count} commit(s) ahead of upstream")
211}
212
213// Helper function to format diverged message
214pub fn format_diverged_message(behind: u32, ahead: u32) -> String {
215    format!("🔀 Branch has diverged: {behind} behind, {ahead} ahead")
216}
217
218// Helper function to format diverged help message
219pub fn format_diverged_help_message() -> &'static str {
220    "💡 Use --merge flag to merge changes, or handle manually"
221}
222
223// Helper function to format sync success message
224pub fn format_sync_success_message(merge: bool) -> String {
225    if merge {
226        "✅ Successfully merged upstream changes".to_string()
227    } else {
228        "✅ Successfully rebased onto upstream".to_string()
229    }
230}