rewrite: mark divergent commits in parent_mapping too

When rebasing descendants, we generally move branches, child commits,
the working copy to the rewritten commit(s). However, we don't move
the working copy to the new rewritten commit (s) if the old commit had
been abandoned, and we don't move child commits if the rewriten was
divergent.

This patch aims to make it clearer that there's only one mapping from
old to new parents, and that is in `parent_mapping`. It does so by
merging the current `divergent` map into it, and makes the `divergent`
just a set instead. When finding the new parents for a child, we leave
the existing parent if it's in the set.

My longer-term goal is to move `parent_mapping`, `abandoned`, and
`divergent` into `MutableRepo` (maybe in a nested struct), so we can
do some transformations on descendants as we rebase them. By having
the state in a single place (not moving it from `MutableRepo` to
`DescendantRebaser` as we currently do), I hope it will be easier to
write a `MutableRepo::transform_descendants(callback)`, where the
callback gets a `CommitBuilder` and can change parents of the commit,
for example.
This commit is contained in:
Martin von Zweigbergk 2024-03-17 16:28:29 -07:00 committed by Martin von Zweigbergk
parent ba244423e8
commit b54ace4954

View file

@ -283,7 +283,7 @@ pub(crate) struct DescendantRebaser<'settings, 'repo> {
// parents). A child of the key commit should be rebased onto all the value commits. A branch
// pointing to the key commit should become a conflict pointing to all the value commits.
parent_mapping: HashMap<CommitId, Vec<CommitId>>,
divergent: HashMap<CommitId, Vec<CommitId>>,
divergent: HashSet<CommitId>,
// In reverse order (parents after children), so we can remove the last one to rebase first.
to_visit: Vec<Commit>,
// Commits to visit but skip. These were also in `to_visit` to start with, but we don't
@ -367,14 +367,15 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
let new_commits = rewritten.values().flatten().cloned().collect();
let mut parent_mapping = HashMap::new();
let mut divergent = HashMap::new();
let mut divergent = HashSet::new();
for (old_commit, new_commits) in rewritten {
if new_commits.len() == 1 {
parent_mapping.insert(old_commit, vec![new_commits.into_iter().next().unwrap()]);
} else {
// The call to index.heads() is mostly to get a predictable order
let new_commits = mut_repo.index().heads(&mut new_commits.iter());
divergent.insert(old_commit, new_commits);
parent_mapping.insert(old_commit.clone(), new_commits);
divergent.insert(old_commit);
}
}
@ -422,6 +423,7 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
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;
@ -430,6 +432,10 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
// 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) => {
@ -455,7 +461,8 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
let mut allowed_iterations = 0..self.parent_mapping.len();
loop {
let made_replacements;
(new_ids, made_replacements) = single_substitution_round(&self.parent_mapping, new_ids);
(new_ids, made_replacements) =
single_substitution_round(&self.parent_mapping, &self.divergent, new_ids);
if !made_replacements {
break;
}
@ -562,12 +569,6 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
self.update_references(old_commit_id, new_parent_ids)?;
return Ok(());
}
if let Some(divergent_ids) = self.divergent.get(&old_commit_id).cloned() {
// Leave divergent commits in place. Don't update `parent_mapping` since we
// don't want to rebase descendants either.
self.update_references(old_commit_id, divergent_ids)?;
return Ok(());
}
let old_parent_ids = old_commit.parent_ids();
let new_parent_ids = self.new_parents(old_parent_ids);
if self.abandoned.contains(&old_commit_id) {