Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
4.5 kB · 126 lines
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127use gix_hash::ObjectId;use gix_object::FindExt;
use super::{Complete, Conflict, EmptyBehavior, Error, Options, Outcome, Pick};
/// Replay `picks` onto `onto`, one cherry-pick at a time, and return the new tip.////// `picks` are the commits to replay, **oldest first**, each with at most one parent.////// `labels` name the sides for conflict markers, and are only observable when `options` are relaxed/// enough to let a marked-up blob through.#[allow(clippy::too_many_arguments)]pub fn replay( onto: &gix_hash::oid, picks: &[ObjectId], committer: &gix_actor::Signature, labels: gix_merge::blob::builtin_driver::text::Labels<'_>, objects: &(impl gix_object::FindObjectOrHeader + gix_object::Write), diff_state: &mut gix_diff::tree::State, diff_resource_cache: &mut gix_diff::blob::Platform, blob_merge: &mut gix_merge::blob::Platform, options: Options,) -> Result<Outcome, Error> { let Options { mut tree_merge, treat_as_unresolved, empty, } = options; tree_merge.fail_on_conflict = Some(treat_as_unresolved);
let empty_tree = ObjectId::empty_tree(onto.kind()); let mut new_parent = onto.to_owned(); let mut new_parent_tree = commit_tree(objects, &new_parent)?; let mut out = Vec::with_capacity(picks.len()); let mut buf = Vec::new();
for pick in picks { let old_commit = objects.find_commit(pick, &mut buf)?.to_owned()?; let old_parent_tree = match old_commit.parents.as_slice() { [] => empty_tree, [one] => commit_tree(objects, one)?, many => { return Err(Error::MergeCommit { id: *pick, parents: many.len(), }); } };
let was_empty = old_parent_tree == old_commit.tree;
// When the new parent's tree is already the commit's own base, the merge would have *ours* // equal to the base and could only ever come out as *theirs*, so take that answer directly. let new_tree = if new_parent_tree == old_parent_tree { old_commit.tree } else { let merge = gix_merge::tree( &old_parent_tree, &new_parent_tree, &old_commit.tree, labels, objects, |buf| objects.write_buf(gix_object::Kind::Blob, buf), diff_state, diff_resource_cache, blob_merge, tree_merge.clone(), ) .map_err(|err| Error::TreeMerge(Box::new(err)))?; if merge .conflicts .iter() .any(|conflict| conflict.is_unresolved(treat_as_unresolved)) { return Ok(Outcome::Conflict(Conflict { at: *pick, onto: new_parent, picks: out, conflicts: merge.conflicts, })); } let mut editor = merge.tree; editor .write(|tree| objects.write(tree)) .map_err(Error::WriteTree)? };
// TODO: gate for only one parent when we support `--rebase-merges` (follow jj behavior) let is_empty = new_parent_tree == new_tree; let should_abandon = match empty { EmptyBehavior::Keep => false, EmptyBehavior::AbandonWasEmpty => is_empty && was_empty, EmptyBehavior::AbandonNewlyEmpty => is_empty && !was_empty, EmptyBehavior::AbandonAllEmpty => is_empty, }; if should_abandon { out.push(Pick::Dropped); continue; }
let mut rewritten = old_commit; rewritten.tree = new_tree; rewritten.parents = std::iter::once(new_parent).collect(); rewritten.committer = committer.clone(); rewritten .extra_headers .retain(|(name, _)| !crate::STALE_HEADERS.iter().any(|stale| name == stale));
new_parent = objects.write(&rewritten).map_err(Error::WriteCommit)?; new_parent_tree = new_tree; out.push(Pick::Rewritten(new_parent)); }
Ok(Outcome::Complete(Complete { tip: new_parent, picks: out, }))}
fn commit_tree( objects: &impl gix_object::FindObjectOrHeader, id: &gix_hash::oid,) -> Result<ObjectId, Error> { let mut buf = Vec::new(); Ok(objects.find_commit(id, &mut buf)?.tree())}