use super::types::{SyncPlan, SyncResult};
use crate::error::Result;
use crate::state::SyncState;
use crate::traits::StateStore;
pub fn execute_sync(
repo: &impl rung_git::GitOps,
state: &impl StateStore,
plan: SyncPlan,
) -> Result<SyncResult> {
if plan.is_empty() {
return Ok(SyncResult::AlreadySynced);
}
let branches_to_backup: Vec<(String, String)> = plan
.branches
.iter()
.map(|action| {
let commit = repo.branch_commit(&action.branch)?;
Ok((action.branch.clone(), commit.to_string()))
})
.collect::<Result<Vec<_>>>()?;
let backup_refs: Vec<(&str, &str)> = branches_to_backup
.iter()
.map(|(b, c)| (b.as_str(), c.as_str()))
.collect();
let backup_id = state.create_backup(&backup_refs)?;
let original_branch = repo.current_branch().ok();
let branch_names: Vec<String> = plan.branches.iter().map(|a| a.branch.clone()).collect();
let mut sync_state = SyncState::new(backup_id.clone(), branch_names);
state.save_sync_state(&sync_state)?;
for action in plan.branches {
repo.checkout(&action.branch)?;
let new_base = rung_git::Oid::from_str(&action.new_base).map_err(|e| {
crate::error::Error::SyncFailed(format!(
"invalid commit '{}' for branch '{}': {e}",
action.new_base, action.branch
))
})?;
match repo.rebase_onto(new_base) {
Ok(()) => {
sync_state.advance();
state.save_sync_state(&sync_state)?;
}
Err(rung_git::Error::RebaseConflict(files)) => {
state.save_sync_state(&sync_state)?;
return Ok(SyncResult::Paused {
at_branch: action.branch,
conflict_files: files,
backup_id,
});
}
Err(e) => {
if repo.is_rebasing() {
let _ = repo.rebase_abort();
}
let _ = state.clear_sync_state();
return Err(e.into());
}
}
}
state.clear_sync_state()?;
if let Some(branch) = original_branch {
let _ = repo.checkout(&branch); }
Ok(SyncResult::Complete {
branches_rebased: sync_state.completed.len(),
backup_id,
})
}
pub fn continue_sync(repo: &impl rung_git::GitOps, state: &impl StateStore) -> Result<SyncResult> {
let mut sync_state = state.load_sync_state()?;
let backup_id = sync_state.backup_id.clone();
if repo.is_rebasing() {
match repo.rebase_continue() {
Ok(()) => {
sync_state.advance();
state.save_sync_state(&sync_state)?;
}
Err(rung_git::Error::RebaseConflict(files)) => {
return Ok(SyncResult::Paused {
at_branch: sync_state.current_branch.clone(),
conflict_files: files,
backup_id,
});
}
Err(e) => {
let _ = repo.rebase_abort();
let _ = state.clear_sync_state();
return Err(e.into());
}
}
} else {
let current_branch = &sync_state.current_branch;
let stack = state.load_stack()?;
let branch = stack
.find_branch(current_branch)
.ok_or_else(|| crate::error::Error::NotInStack(current_branch.clone()))?;
let default_branch = state.default_branch()?;
let parent_name = branch.parent.as_deref().unwrap_or(&default_branch);
let parent_commit = repo.branch_commit(parent_name)?;
let current_commit = repo.branch_commit(current_branch)?;
let merge_base = repo.merge_base(parent_commit, current_commit)?;
if merge_base != parent_commit {
return Err(crate::error::Error::SyncFailed(format!(
"Rebase verification failed for '{current_branch}': parent '{parent_name}' \
is not an ancestor. The rebase may not have completed correctly. \
Please run `rung sync --abort` and try again."
)));
}
sync_state.advance();
state.save_sync_state(&sync_state)?;
}
let stack = state.load_stack()?;
let default_branch = state.default_branch()?;
while !sync_state.current_branch.is_empty() {
let branch_name = sync_state.current_branch.clone();
repo.checkout(&branch_name)?;
let branch = stack
.find_branch(&branch_name)
.ok_or_else(|| crate::error::Error::NotInStack(branch_name.clone()))?;
let parent_name = branch.parent.as_deref().unwrap_or(&default_branch);
let parent_commit = repo.branch_commit(parent_name)?;
match repo.rebase_onto(parent_commit) {
Ok(()) => {
sync_state.advance();
state.save_sync_state(&sync_state)?;
}
Err(rung_git::Error::RebaseConflict(files)) => {
state.save_sync_state(&sync_state)?;
return Ok(SyncResult::Paused {
at_branch: branch_name,
conflict_files: files,
backup_id,
});
}
Err(e) => {
if repo.is_rebasing() {
let _ = repo.rebase_abort();
}
let _ = state.clear_sync_state();
return Err(e.into());
}
}
}
state.clear_sync_state()?;
Ok(SyncResult::Complete {
branches_rebased: sync_state.completed.len(),
backup_id,
})
}
pub fn abort_sync(repo: &impl rung_git::GitOps, state: &impl StateStore) -> Result<()> {
let sync_state = state.load_sync_state()?;
if repo.is_rebasing() {
repo.rebase_abort()?;
}
let refs = state.load_backup(&sync_state.backup_id)?;
for (branch_name, sha) in refs {
let oid = rung_git::Oid::from_str(&sha).map_err(|e| {
crate::error::Error::SyncFailed(format!(
"invalid backup commit '{sha}' for branch '{branch_name}': {e}"
))
})?;
repo.reset_branch(&branch_name, oid)?;
}
state.clear_sync_state()?;
Ok(())
}