1use std::process::Command;
2
3pub fn run(merge: bool) {
4 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 let upstream = match get_upstream_branch(¤t_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(¤t_branch, &upstream));
23
24 if let Err(msg) = fetch_upstream(&upstream) {
26 eprintln!("{}", format_error_message(msg));
27 return;
28 }
29
30 let status = match get_sync_status(¤t_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), }
76
77fn 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
91fn 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
105fn 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
121fn 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
149pub 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
164fn 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
188pub fn format_sync_start_message(branch: &str, upstream: &str) -> String {
190 format!("🔄 Syncing branch '{branch}' with '{upstream}'...")
191}
192
193pub fn format_error_message(msg: &str) -> String {
195 format!("❌ {msg}")
196}
197
198pub fn format_up_to_date_message() -> &'static str {
200 "✅ Branch is up to date with upstream"
201}
202
203pub fn format_behind_message(count: u32) -> String {
205 format!("⬇️ Branch is {count} commit(s) behind upstream")
206}
207
208pub fn format_ahead_message(count: u32) -> String {
210 format!("⬆️ Branch is {count} commit(s) ahead of upstream")
211}
212
213pub fn format_diverged_message(behind: u32, ahead: u32) -> String {
215 format!("🔀 Branch has diverged: {behind} behind, {ahead} ahead")
216}
217
218pub fn format_diverged_help_message() -> &'static str {
220 "💡 Use --merge flag to merge changes, or handle manually"
221}
222
223pub 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}