1use crate::GitXError;
2use crate::command::Command;
3use crate::core::git::{GitOperations, RemoteOperations};
4use std::process::Command as StdCommand;
5
6pub fn run(merge: bool) -> Result<(), GitXError> {
7 let cmd = SyncCommand;
8 cmd.execute(merge)
9}
10
11pub struct SyncCommand;
13
14impl Command for SyncCommand {
15 type Input = bool;
16 type Output = ();
17
18 fn execute(&self, merge: bool) -> Result<(), GitXError> {
19 run_sync(merge)
20 }
21
22 fn name(&self) -> &'static str {
23 "sync"
24 }
25
26 fn description(&self) -> &'static str {
27 "Sync current branch with its upstream"
28 }
29}
30
31fn run_sync(merge: bool) -> Result<(), GitXError> {
32 let current_branch = GitOperations::current_branch()
34 .map_err(|e| GitXError::GitCommand(format!("Failed to get current branch: {e}")))?;
35
36 let upstream = GitOperations::upstream_branch()
38 .map_err(|e| GitXError::GitCommand(format!("Failed to get upstream branch: {e}")))?;
39
40 println!(
41 "🔄 Syncing branch '{}' with '{}'...",
42 ¤t_branch, &upstream
43 );
44
45 let remote = upstream.split('/').next().unwrap_or("origin");
47 RemoteOperations::fetch(Some(remote))
48 .map_err(|e| GitXError::GitCommand(format!("Failed to fetch from remote: {e}")))?;
49
50 let (ahead, behind) = GitOperations::ahead_behind_counts()
52 .map_err(|e| GitXError::GitCommand(format!("Failed to get ahead/behind counts: {e}")))?;
53
54 let status = match (behind, ahead) {
55 (0, 0) => SyncStatus::UpToDate,
56 (b, 0) if b > 0 => SyncStatus::Behind(b),
57 (0, a) if a > 0 => SyncStatus::Ahead(a),
58 (b, a) if b > 0 && a > 0 => SyncStatus::Diverged(b, a),
59 _ => SyncStatus::UpToDate,
60 };
61
62 match status {
63 SyncStatus::UpToDate => {
64 println!("✅ Branch is up to date with upstream");
65 }
66 SyncStatus::Behind(count) => {
67 println!("{count}");
68 let args = if merge {
69 vec!["merge", &upstream]
70 } else {
71 vec!["rebase", &upstream]
72 };
73
74 match GitOperations::run_status(&args) {
75 Err(e) => return Err(GitXError::GitCommand(format!("Sync failed: {e}"))),
76 Ok(()) => println!("{}", format_sync_success_message(merge)),
77 }
78 }
79 SyncStatus::Ahead(count) => {
80 println!("{count}");
81 }
82 SyncStatus::Diverged(behind, ahead) => {
83 println!("🔀 Branch has diverged: {behind} behind, {ahead} ahead");
84 if merge {
85 let args = if merge {
86 vec!["merge", &upstream]
87 } else {
88 vec!["rebase", &upstream]
89 };
90
91 match GitOperations::run_status(&args) {
92 Err(e) => return Err(GitXError::GitCommand(format!("Sync failed: {e}"))),
93 Ok(()) => println!("{}", format_sync_success_message(merge)),
94 }
95 } else {
96 println!("💡 Use --merge flag to merge changes, or handle manually");
97 }
98 }
99 }
100
101 Ok(())
102}
103
104#[derive(Debug, PartialEq)]
105pub enum SyncStatus {
106 UpToDate,
107 Behind(u32),
108 Ahead(u32),
109 Diverged(u32, u32), }
111
112pub fn get_upstream_branch(branch: &str) -> Result<String, &'static str> {
113 let output = StdCommand::new("git")
114 .args(["rev-parse", "--abbrev-ref", &format!("{branch}@{{u}}")])
115 .output()
116 .map_err(|_| "Failed to get upstream branch")?;
117
118 if !output.status.success() {
119 return Err("No upstream branch configured");
120 }
121
122 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
123}
124
125pub fn fetch_upstream(upstream: &str) -> Result<(), &'static str> {
126 let remote = upstream.split('/').next().unwrap_or("origin");
127
128 let status = StdCommand::new("git")
129 .args(["fetch", remote])
130 .status()
131 .map_err(|_| "Failed to execute fetch command")?;
132
133 if !status.success() {
134 return Err("Failed to fetch from remote");
135 }
136
137 Ok(())
138}
139
140pub fn get_sync_status(branch: &str, upstream: &str) -> Result<SyncStatus, &'static str> {
141 let output = StdCommand::new("git")
142 .args([
143 "rev-list",
144 "--left-right",
145 "--count",
146 &format!("{upstream}...{branch}"),
147 ])
148 .output()
149 .map_err(|_| "Failed to get sync status")?;
150
151 if !output.status.success() {
152 return Err("Failed to compare with upstream");
153 }
154
155 let counts = String::from_utf8_lossy(&output.stdout);
156 let (behind, ahead) = parse_sync_counts(&counts)?;
157
158 Ok(match (behind, ahead) {
159 (0, 0) => SyncStatus::UpToDate,
160 (b, 0) if b > 0 => SyncStatus::Behind(b),
161 (0, a) if a > 0 => SyncStatus::Ahead(a),
162 (b, a) if b > 0 && a > 0 => SyncStatus::Diverged(b, a),
163 _ => SyncStatus::UpToDate,
164 })
165}
166
167pub fn parse_sync_counts(output: &str) -> Result<(u32, u32), &'static str> {
168 let mut parts = output.split_whitespace();
169 let behind = parts
170 .next()
171 .and_then(|s| s.parse().ok())
172 .ok_or("Invalid sync count format")?;
173 let ahead = parts
174 .next()
175 .and_then(|s| s.parse().ok())
176 .ok_or("Invalid sync count format")?;
177
178 Ok((behind, ahead))
179}
180
181pub fn sync_with_upstream(upstream: &str, merge: bool) -> Result<(), &'static str> {
182 let args = if merge {
183 ["merge", upstream]
184 } else {
185 ["rebase", upstream]
186 };
187
188 let status = StdCommand::new("git")
189 .args(args)
190 .status()
191 .map_err(|_| "Failed to execute sync command")?;
192
193 if !status.success() {
194 return Err(if merge {
195 "Merge failed"
196 } else {
197 "Rebase failed"
198 });
199 }
200
201 Ok(())
202}
203
204pub fn format_sync_success_message(merge: bool) -> String {
206 if merge {
207 "✅ Successfully merged upstream changes".to_string()
208 } else {
209 "✅ Successfully rebased onto upstream".to_string()
210 }
211}