eazygit/commands/git_commands/
rebase.rs1use crate::commands::{Command, CommandResult};
4use crate::services::GitService;
5use crate::app::{AppState, Action, reducer};
6use crate::app::rebase::{RebaseOperations, operations::{RebaseResult, RebaseRecovery}};
7use crate::errors::CommandError;
8use tracing::instrument;
9
10#[cfg(test)]
11mod tests {
12 use super::*;
13 use crate::app::AppState;
14 use crate::services::GitService;
15 use crate::commands::CommandResult;
16 use std::sync::Arc;
17
18 fn create_test_state() -> AppState {
19 let mut state = AppState::new();
20 state.repo_path = ".".to_string();
21 state
22 }
23
24 fn create_mock_git_service() -> Arc<GitService> {
25 Arc::new(GitService::new())
26 }
27
28 #[test]
29 fn test_rebase_save_and_run_command_creation() {
30 let command = RebaseSaveAndRunCommand;
31 assert!(true); }
34
35 #[test]
36 fn test_rebase_continue_command_creation() {
37 let command = RebaseContinueCommand;
38 assert!(true); }
40
41 #[test]
42 fn test_rebase_skip_command_creation() {
43 let command = RebaseSkipCommand;
44 assert!(true); }
46
47 #[test]
48 fn test_rebase_abort_command_creation() {
49 let command = RebaseAbortCommand;
50 assert!(true); }
52
53 #[test]
54 fn test_rebase_resolve_conflicts_command_creation() {
55 let command = RebaseResolveConflictsCommand;
56 assert!(true); }
58
59 #[test]
60 fn test_rebase_abort_from_conflict_command_creation() {
61 let command = RebaseAbortFromConflictCommand;
62 assert!(true); }
64
65 #[test]
66 fn test_rebase_continue_interrupted_command_creation() {
67 let command = RebaseContinueInterruptedCommand;
68 assert!(true); }
70
71 #[test]
72 fn test_rebase_abort_interrupted_command_creation() {
73 let command = RebaseAbortInterruptedCommand;
74 assert!(true); }
76}
77
78pub struct RebaseSaveAndRunCommand;
80
81pub struct RebaseContinueCommand;
83
84pub struct RebaseSkipCommand;
86
87pub struct RebaseAbortCommand;
89
90pub struct RebaseResolveConflictsCommand;
92
93pub struct RebaseAbortFromConflictCommand;
95
96pub struct RebaseContinueInterruptedCommand;
98
99pub struct RebaseAbortInterruptedCommand;
101
102impl Command for RebaseSaveAndRunCommand {
103 #[instrument(skip(self, git, state))]
104 fn execute(
105 &self,
106 git: &GitService,
107 state: &AppState,
108 ) -> Result<CommandResult, CommandError> {
109 let mut new_state = state.clone();
110
111 match RebaseOperations::execute_rebase(git, &state.repo_path, &mut new_state.rebase_session) {
113 Ok(result) => {
114 match result {
115 RebaseResult::Success => {
116 new_state = reducer(new_state, Action::SetFeedback(Some("Rebase started successfully".to_string())));
118 new_state.rebase_editor_open = false;
119 }
120 RebaseResult::Conflicts { .. } => {
121 new_state = reducer(new_state, Action::SetFeedback(Some("Rebase started but conflicts detected".to_string())));
123 new_state.rebase_editor_open = false;
124 }
125 _ => {
126 new_state = reducer(new_state, Action::SetStatusError(Some("Unexpected rebase result".to_string())));
128 }
129 }
130 Ok(CommandResult::StateUpdate(new_state))
131 }
132 Err(e) => {
133 let error_msg = format!("Failed to start rebase: {}", e);
135 new_state = reducer(new_state, Action::SetStatusError(Some(error_msg.clone())));
136 Err(CommandError::GitError(crate::errors::GitError::OperationError(anyhow::anyhow!(error_msg))))
137 }
138 }
139 }
140}
141
142impl Command for RebaseContinueCommand {
143 #[instrument(skip(self, git, state))]
144 fn execute(
145 &self,
146 git: &GitService,
147 state: &AppState,
148 ) -> Result<CommandResult, CommandError> {
149 let mut new_state = state.clone();
150
151 match RebaseOperations::continue_rebase(git, &state.repo_path, &mut new_state.rebase_session) {
152 Ok(result) => {
153 match result {
154 RebaseResult::Success => {
155 new_state = reducer(new_state, Action::SetFeedback(Some("Rebase continued successfully".to_string())));
156 }
157 RebaseResult::Completed => {
158 new_state = reducer(new_state, Action::SetFeedback(Some("Rebase completed successfully".to_string())));
159 new_state.rebase_session = crate::app::rebase::RebaseSession::default();
161 }
162 RebaseResult::Conflicts { .. } => {
163 new_state = reducer(new_state, Action::SetFeedback(Some("Conflicts detected during continue".to_string())));
164 }
165 _ => {
166 new_state = reducer(new_state, Action::SetStatusError(Some("Unexpected rebase result during continue".to_string())));
167 }
168 }
169 Ok(CommandResult::StateUpdate(new_state))
170 }
171 Err(e) => {
172 let error_msg = format!("Failed to continue rebase: {}", e);
173 new_state = reducer(new_state, Action::SetStatusError(Some(error_msg.clone())));
174 Err(CommandError::GitError(crate::errors::GitError::OperationError(anyhow::anyhow!(error_msg))))
175 }
176 }
177 }
178}
179
180impl Command for RebaseSkipCommand {
181 #[instrument(skip(self, git, state))]
182 fn execute(
183 &self,
184 git: &GitService,
185 state: &AppState,
186 ) -> Result<CommandResult, CommandError> {
187 let mut new_state = state.clone();
188
189 match RebaseOperations::skip_rebase_step(git, &state.repo_path, &mut new_state.rebase_session) {
190 Ok(result) => {
191 match result {
192 RebaseResult::Success => {
193 new_state = reducer(new_state, Action::SetFeedback(Some("Skipped current commit, rebase continued".to_string())));
194 }
195 RebaseResult::Completed => {
196 new_state = reducer(new_state, Action::SetFeedback(Some("Rebase completed successfully".to_string())));
197 new_state.rebase_session = crate::app::rebase::RebaseSession::default();
198 }
199 RebaseResult::Conflicts { .. } => {
200 new_state = reducer(new_state, Action::SetFeedback(Some("Conflicts detected after skipping".to_string())));
201 }
202 _ => {
203 new_state = reducer(new_state, Action::SetStatusError(Some("Unexpected rebase result during skip".to_string())));
204 }
205 }
206 Ok(CommandResult::StateUpdate(new_state))
207 }
208 Err(e) => {
209 let error_msg = format!("Failed to skip rebase step: {}", e);
210 new_state = reducer(new_state, Action::SetStatusError(Some(error_msg.clone())));
211 Err(CommandError::GitError(crate::errors::GitError::OperationError(anyhow::anyhow!(error_msg))))
212 }
213 }
214 }
215}
216
217impl Command for RebaseAbortCommand {
218 #[instrument(skip(self, git, state))]
219 fn execute(
220 &self,
221 git: &GitService,
222 state: &AppState,
223 ) -> Result<CommandResult, CommandError> {
224 let mut new_state = state.clone();
225
226 match RebaseOperations::abort_rebase(git, &state.repo_path, &mut new_state.rebase_session) {
227 Ok(result) => {
228 match result {
229 RebaseResult::Aborted => {
230 new_state = reducer(new_state, Action::SetFeedback(Some("Rebase aborted successfully".to_string())));
231 new_state.rebase_session = crate::app::rebase::RebaseSession::default();
233 }
234 _ => {
235 new_state = reducer(new_state, Action::SetStatusError(Some("Unexpected rebase result during abort".to_string())));
236 }
237 }
238 Ok(CommandResult::StateUpdate(new_state))
239 }
240 Err(e) => {
241 let error_msg = format!("Failed to abort rebase: {}", e);
242 new_state = reducer(new_state, Action::SetStatusError(Some(error_msg.clone())));
243 Err(CommandError::GitError(crate::errors::GitError::OperationError(anyhow::anyhow!(error_msg))))
244 }
245 }
246 }
247}
248
249impl Command for RebaseResolveConflictsCommand {
250 #[instrument(skip(self, git, state))]
251 fn execute(
252 &self,
253 git: &GitService,
254 state: &AppState,
255 ) -> Result<CommandResult, CommandError> {
256 let mut new_state = state.clone();
257
258 match git.status_porcelain(&state.repo_path) {
260 Ok(status) => {
261 if status.contains("U ") || status.contains("AA ") || status.contains("DD ") {
263 new_state = reducer(new_state, Action::SetStatusError(Some("Conflicts still exist. Please resolve all conflicts before continuing.".to_string())));
264 return Ok(CommandResult::StateUpdate(new_state));
265 }
266
267 if !status.lines().any(|line| line.starts_with("M ") || line.starts_with("A ") || line.starts_with("D ")) {
269 new_state = reducer(new_state, Action::SetStatusError(Some("No staged changes found. Please stage your conflict resolutions.".to_string())));
270 return Ok(CommandResult::StateUpdate(new_state));
271 }
272
273 match RebaseOperations::continue_rebase(git, &state.repo_path, &mut new_state.rebase_session) {
275 Ok(result) => {
276 match result {
277 RebaseResult::Success => {
278 new_state = reducer(new_state, Action::SetFeedback(Some("Conflicts resolved, rebase continued".to_string())));
279 }
280 RebaseResult::Completed => {
281 new_state = reducer(new_state, Action::SetFeedback(Some("Rebase completed successfully after conflict resolution".to_string())));
282 new_state.rebase_session = crate::app::rebase::RebaseSession::default();
283 }
284 RebaseResult::Conflicts { .. } => {
285 new_state = reducer(new_state, Action::SetFeedback(Some("Additional conflicts detected".to_string())));
286 }
287 _ => {
288 new_state = reducer(new_state, Action::SetStatusError(Some("Unexpected rebase result after conflict resolution".to_string())));
289 }
290 }
291 Ok(CommandResult::StateUpdate(new_state))
292 }
293 Err(e) => {
294 let error_msg = format!("Failed to continue rebase after conflict resolution: {}", e);
295 new_state = reducer(new_state, Action::SetStatusError(Some(error_msg.clone())));
296 Err(CommandError::GitError(crate::errors::GitError::OperationError(anyhow::anyhow!(error_msg))))
297 }
298 }
299 }
300 Err(e) => {
301 let error_msg = format!("Failed to check repository status: {}", e);
302 new_state = reducer(new_state, Action::SetStatusError(Some(error_msg.clone())));
303 Err(CommandError::GitError(crate::errors::GitError::OperationError(anyhow::anyhow!(error_msg))))
304 }
305 }
306 }
307}
308
309impl Command for RebaseAbortFromConflictCommand {
310 #[instrument(skip(self, git, state))]
311 fn execute(
312 &self,
313 git: &GitService,
314 state: &AppState,
315 ) -> Result<CommandResult, CommandError> {
316 let mut new_state = state.clone();
317
318 match RebaseOperations::abort_rebase(git, &state.repo_path, &mut new_state.rebase_session) {
319 Ok(result) => {
320 match result {
321 RebaseResult::Aborted => {
322 new_state = reducer(new_state, Action::SetFeedback(Some("Rebase aborted from conflict state".to_string())));
323 new_state.rebase_session = crate::app::rebase::RebaseSession::default();
325 }
326 _ => {
327 new_state = reducer(new_state, Action::SetStatusError(Some("Unexpected rebase result during abort from conflict".to_string())));
328 }
329 }
330 Ok(CommandResult::StateUpdate(new_state))
331 }
332 Err(e) => {
333 let error_msg = format!("Failed to abort rebase from conflict state: {}", e);
334 new_state = reducer(new_state, Action::SetStatusError(Some(error_msg.clone())));
335 Err(CommandError::GitError(crate::errors::GitError::OperationError(anyhow::anyhow!(error_msg))))
336 }
337 }
338 }
339}
340
341impl Command for RebaseContinueInterruptedCommand {
342 #[instrument(skip(self, git, state))]
343 fn execute(
344 &self,
345 git: &GitService,
346 state: &AppState,
347 ) -> Result<CommandResult, CommandError> {
348 let mut new_state = state.clone();
349
350 match RebaseRecovery::continue_interrupted_rebase(git, &state.repo_path, &mut new_state.rebase_session) {
351 Ok(result) => {
352 match result {
353 RebaseResult::Success => {
354 new_state = reducer(new_state, Action::SetFeedback(Some("Continued interrupted rebase".to_string())));
355 new_state.rebase_recovery_open = false;
356 }
357 RebaseResult::Completed => {
358 new_state = reducer(new_state, Action::SetFeedback(Some("Rebase completed successfully".to_string())));
359 new_state.rebase_session = crate::app::rebase::RebaseSession::default();
360 new_state.rebase_recovery_open = false;
361 }
362 RebaseResult::Conflicts { .. } => {
363 new_state = reducer(new_state, Action::SetFeedback(Some("Conflicts detected in interrupted rebase".to_string())));
364 new_state.rebase_recovery_open = false;
365 }
366 _ => {
367 new_state = reducer(new_state, Action::SetStatusError(Some("Unexpected rebase result during recovery".to_string())));
368 }
369 }
370 Ok(CommandResult::StateUpdate(new_state))
371 }
372 Err(e) => {
373 let error_msg = format!("Failed to continue interrupted rebase: {}", e);
374 new_state = reducer(new_state, Action::SetStatusError(Some(error_msg.clone())));
375 Err(CommandError::GitError(crate::errors::GitError::OperationError(anyhow::anyhow!(error_msg))))
376 }
377 }
378 }
379}
380
381impl Command for RebaseAbortInterruptedCommand {
382 #[instrument(skip(self, git, state))]
383 fn execute(
384 &self,
385 git: &GitService,
386 state: &AppState,
387 ) -> Result<CommandResult, CommandError> {
388 let mut new_state = state.clone();
389
390 match RebaseRecovery::abort_interrupted_rebase(git, &state.repo_path) {
391 Ok(()) => {
392 new_state = reducer(new_state, Action::SetFeedback(Some("Aborted interrupted rebase".to_string())));
393 new_state.rebase_session = crate::app::rebase::RebaseSession::default();
395 new_state.rebase_recovery_open = false;
396 Ok(CommandResult::StateUpdate(new_state))
397 }
398 Err(e) => {
399 let error_msg = format!("Failed to abort interrupted rebase: {}", e);
400 new_state = reducer(new_state, Action::SetStatusError(Some(error_msg.clone())));
401 Err(CommandError::GitError(crate::errors::GitError::OperationError(anyhow::anyhow!(error_msg))))
402 }
403 }
404 }
405}
406