use crate::merge_isolation_changes::{Change, WorktreeState, parse_raw_changes, worktree_state};
use crate::merge_isolation_journal::MergeIntent;
use crate::merge_isolation_recovery::{
MergeRecoveryAction, RecoveryPlace, Standing, index_holds_the_merge, install_entries, merge_changes, remove_record,
standing,
};
use crate::recovery_error::RecoveryError;
use crate::transaction_git::{GitRequest, run_git_checked};
use std::path::Path;
pub(crate) fn settle_moved_branch(
place: &RecoveryPlace,
record: &Path,
intent: &MergeIntent,
moved_to: &str,
) -> Result<MergeRecoveryAction, RecoveryError> {
let root: &Path = Path::new(intent.repository_root.as_str());
let changes: Vec<Change> = merge_changes(place, root, intent)?;
let mut states: Vec<WorktreeState> = Vec::with_capacity(changes.len());
for change in &changes {
states.push(worktree_state(&place.git, root, change.path.as_slice()).map_err(RecoveryError)?);
}
let began: bool = index_holds_the_merge(place, root, intent.old_oid.as_str(), changes.as_slice())?
|| changes.iter().zip(states.iter()).any(|(change, state)| { return standing(change, state) == Standing::New; });
if !began {
remove_record(record)?;
return Ok(MergeRecoveryAction::BranchMoved);
}
let request: GitRequest = GitRequest::new(
root,
&["diff-tree", "-r", "-z", "--raw", "--no-renames", "--no-abbrev", intent.new_oid.as_str(), moved_to],
);
let output = run_git_checked(&place.git, &request).map_err(|error| { return RecoveryError(error.to_string()); })?;
let beyond: Vec<Change> = parse_raw_changes(output.stdout.as_slice()).map_err(RecoveryError)?;
let mut ends: Vec<Change> = Vec::with_capacity(changes.len());
for change in &changes {
let (mode, oid): (String, String) = beyond
.iter()
.find(|moved| { return moved.path == change.path; })
.map_or_else(|| { return (change.new_mode.clone(), change.new_oid.clone()); }, |moved| { return (moved.new_mode.clone(), moved.new_oid.clone()); });
ends.push(Change {
path: change.path.clone(),
old_mode: change.new_mode.clone(),
new_mode: mode,
old_oid: change.new_oid.clone(),
new_oid: oid,
});
}
let mut foreign: Vec<String> = Vec::new();
let mut held: Vec<Standing> = Vec::with_capacity(ends.len());
for ((change, end), state) in changes.iter().zip(ends.iter()).zip(states.iter()) {
let holds_old_or_merged: bool = standing(change, state) != Standing::Foreign;
let holds_end: bool = match state {
WorktreeState::Missing => !end.exists_after(),
WorktreeState::Content(oid) => end.exists_after() && *oid == end.new_oid,
WorktreeState::Other => false,
};
if !holds_old_or_merged && !holds_end {
foreign.push(String::from_utf8_lossy(change.path.as_slice()).into_owned());
}
held.push(if holds_end { Standing::New } else { Standing::Old });
}
if !foreign.is_empty() {
return Err(RecoveryError(format!(
"A merge in {} lost the branch to a newer commit while Git updated these files, and they now hold neither \
their old content, the merge's nor the newer commit's: {}. Nothing was changed and the record {} was kept. \
Save your edits to them elsewhere, restore them with `git restore -- <path>`, then run any git command again.",
root.display(),
foreign.join(", "),
record.display()
)));
}
install_entries(place, root, ends.as_slice(), held.as_slice())?;
remove_record(record)?;
return Ok(MergeRecoveryAction::BranchMoved);
}