rewrite: move new_parents() to MutableRepo

The function only uses state from `MutableRepo`, so it should be
implemented on that type.
This commit is contained in:
Martin von Zweigbergk 2024-03-24 15:27:42 -07:00 committed by Martin von Zweigbergk
parent cfdb341c6b
commit 0f7a86d725
2 changed files with 66 additions and 65 deletions

View file

@ -892,6 +892,70 @@ impl MutableRepo {
!(self.parent_mapping.is_empty() && self.abandoned.is_empty())
}
/// Calculates new parents for a commit that's currently based on the given
/// parents. It does that by considering how previous commits have been
/// rewritten and abandoned.
///
/// Panics if `parent_mapping` contains cycles
pub fn new_parents(&self, old_ids: &[CommitId]) -> Vec<CommitId> {
fn single_substitution_round(
parent_mapping: &HashMap<CommitId, Vec<CommitId>>,
divergent: &HashSet<CommitId>,
ids: Vec<CommitId>,
) -> (Vec<CommitId>, bool) {
let mut made_replacements = false;
let mut new_ids = vec![];
// TODO(ilyagr): (Maybe?) optimize common case of replacements all
// being singletons. If CommitId-s were Copy. no allocations would be needed in
// that case, but it probably doesn't matter much while they are Vec<u8>-s.
for id in ids.into_iter() {
if divergent.contains(&id) {
new_ids.push(id);
continue;
}
match parent_mapping.get(&id) {
None => new_ids.push(id),
Some(replacements) => {
assert!(
// Each commit must have a parent, so a parent can
// not just be mapped to nothing. This assertion
// could be removed if this function is used for
// mapping something other than a commit's parents.
!replacements.is_empty(),
"Found empty value for key {id:?} in the parent mapping",
);
made_replacements = true;
new_ids.extend(replacements.iter().cloned())
}
};
}
(new_ids, made_replacements)
}
let mut new_ids: Vec<CommitId> = old_ids.to_vec();
// The longest possible non-cycle substitution sequence goes through each key of
// parent_mapping once.
let mut allowed_iterations = 0..self.parent_mapping.len();
loop {
let made_replacements;
(new_ids, made_replacements) =
single_substitution_round(&self.parent_mapping, &self.divergent, new_ids);
if !made_replacements {
break;
}
allowed_iterations
.next()
.expect("cycle detected in the parent mapping");
}
match new_ids.as_slice() {
// The first two cases are an optimization for the common case of commits with <=2
// parents
[_singleton] => new_ids,
[a, b] if a != b => new_ids,
_ => new_ids.into_iter().unique().collect(),
}
}
/// After the rebaser returned by this function is dropped,
/// self.clear_descendant_rebaser_plans() needs to be called.
fn rebase_descendants_return_rebaser<'settings, 'repo>(

View file

@ -393,69 +393,6 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
self.rebased
}
/// Panics if `parent_mapping` contains cycles
fn new_parents(&self, old_ids: &[CommitId]) -> Vec<CommitId> {
fn single_substitution_round(
parent_mapping: &HashMap<CommitId, Vec<CommitId>>,
divergent: &HashSet<CommitId>,
ids: Vec<CommitId>,
) -> (Vec<CommitId>, bool) {
let mut made_replacements = false;
let mut new_ids = vec![];
// TODO(ilyagr): (Maybe?) optimize common case of replacements all
// being singletons. If CommitId-s were Copy. no allocations would be needed in
// that case, but it probably doesn't matter much while they are Vec<u8>-s.
for id in ids.into_iter() {
if divergent.contains(&id) {
new_ids.push(id);
continue;
}
match parent_mapping.get(&id) {
None => new_ids.push(id),
Some(replacements) => {
assert!(
// Each commit must have a parent, so a parent can
// not just be mapped to nothing. This assertion
// could be removed if this function is used for
// mapping something other than a commit's parents.
!replacements.is_empty(),
"Found empty value for key {id:?} in the parent mapping",
);
made_replacements = true;
new_ids.extend(replacements.iter().cloned())
}
};
}
(new_ids, made_replacements)
}
let mut new_ids: Vec<CommitId> = old_ids.to_vec();
// The longest possible non-cycle substitution sequence goes through each key of
// parent_mapping once.
let mut allowed_iterations = 0..self.mut_repo.parent_mapping.len();
loop {
let made_replacements;
(new_ids, made_replacements) = single_substitution_round(
&self.mut_repo.parent_mapping,
&self.mut_repo.divergent,
new_ids,
);
if !made_replacements {
break;
}
allowed_iterations
.next()
.expect("cycle detected in the parent mapping");
}
match new_ids.as_slice() {
// The first two cases are an optimization for the common case of commits with <=2
// parents
[_singleton] => new_ids,
[a, b] if a != b => new_ids,
_ => new_ids.into_iter().unique().collect(),
}
}
fn ref_target_update(old_id: CommitId, new_ids: Vec<CommitId>) -> (RefTarget, RefTarget) {
let old_ids = std::iter::repeat(old_id).take(new_ids.len());
(
@ -539,7 +476,7 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
return Ok(());
}
let old_parent_ids = old_commit.parent_ids();
let new_parent_ids = self.new_parents(old_parent_ids);
let new_parent_ids = self.mut_repo.new_parents(old_parent_ids);
if new_parent_ids == old_parent_ids {
// The commit is already in place.
return Ok(());
@ -579,7 +516,7 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
// mappings, not transitive ones.
// TODO: keep parent_mapping updated with transitive mappings so we don't need
// to call `new_parents()` here.
let new_parent_ids = self.new_parents(&new_parent_ids);
let new_parent_ids = self.mut_repo.new_parents(&new_parent_ids);
self.update_references(old_parent_id, new_parent_ids)?;
}
Ok(())