rewrite: split move_commits() into immutable/mutable parts

This makes it clear that the repo state isn't mutated during computation of the
destination and descendants. Still compute_move_commits() is big, so we might
want to split it further. If needed, maybe we can add variants of
apply_move_commits() to fix up intermediate predecessor chains?

compute_move_commits() needs (immutable) &MutableRepo because it depends on
repo.find_descendants_for_rebase().
This commit is contained in:
Yuya Nishihara 2025-05-09 11:58:36 +09:00
parent cb2944e287
commit b9e4e4fe8a

View File

@ -389,7 +389,6 @@ pub struct RewriteRefsOptions {
pub delete_abandoned_bookmarks: bool, pub delete_abandoned_bookmarks: bool,
} }
#[derive(Default)]
pub struct MoveCommitsStats { pub struct MoveCommitsStats {
/// The number of commits in the target set which were rebased. /// The number of commits in the target set which were rebased.
pub num_rebased_targets: u32, pub num_rebased_targets: u32,
@ -419,6 +418,23 @@ pub enum MoveCommitsTarget {
Roots(Vec<CommitId>), Roots(Vec<CommitId>),
} }
#[derive(Clone, Debug)]
struct ComputedMoveCommits {
target_commit_ids: IndexSet<CommitId>,
descendants: Vec<Commit>,
commit_new_parents_map: HashMap<CommitId, Vec<CommitId>>,
}
impl ComputedMoveCommits {
fn empty() -> Self {
ComputedMoveCommits {
target_commit_ids: IndexSet::new(),
descendants: vec![],
commit_new_parents_map: HashMap::new(),
}
}
}
/// Moves `loc.target` commits from their current location to a new location in /// Moves `loc.target` commits from their current location to a new location in
/// the graph. /// the graph.
/// ///
@ -432,6 +448,14 @@ pub fn move_commits(
loc: &MoveCommitsLocation, loc: &MoveCommitsLocation,
options: &RebaseOptions, options: &RebaseOptions,
) -> BackendResult<MoveCommitsStats> { ) -> BackendResult<MoveCommitsStats> {
let commits = compute_move_commits(mut_repo, loc)?;
apply_move_commits(mut_repo, commits, options)
}
fn compute_move_commits(
repo: &MutableRepo,
loc: &MoveCommitsLocation,
) -> BackendResult<ComputedMoveCommits> {
let target_commit_ids: IndexSet<CommitId>; let target_commit_ids: IndexSet<CommitId>;
let connected_target_commits: Vec<Commit>; let connected_target_commits: Vec<Commit>;
let connected_target_commits_internal_parents: HashMap<CommitId, Vec<CommitId>>; let connected_target_commits_internal_parents: HashMap<CommitId, Vec<CommitId>>;
@ -440,17 +464,17 @@ pub fn move_commits(
match &loc.target { match &loc.target {
MoveCommitsTarget::Commits(commit_ids) => { MoveCommitsTarget::Commits(commit_ids) => {
if commit_ids.is_empty() { if commit_ids.is_empty() {
return Ok(MoveCommitsStats::default()); return Ok(ComputedMoveCommits::empty());
} }
target_commit_ids = commit_ids.iter().cloned().collect(); target_commit_ids = commit_ids.iter().cloned().collect();
connected_target_commits = RevsetExpression::commits(commit_ids.clone()) connected_target_commits = RevsetExpression::commits(commit_ids.clone())
.connected() .connected()
.evaluate(mut_repo) .evaluate(repo)
.map_err(|err| err.into_backend_error())? .map_err(|err| err.into_backend_error())?
.iter() .iter()
.commits(mut_repo.store()) .commits(repo.store())
.try_collect() .try_collect()
.map_err(|err| err.into_backend_error())?; .map_err(|err| err.into_backend_error())?;
connected_target_commits_internal_parents = connected_target_commits_internal_parents =
@ -466,12 +490,12 @@ pub fn move_commits(
} }
MoveCommitsTarget::Roots(root_ids) => { MoveCommitsTarget::Roots(root_ids) => {
if root_ids.is_empty() { if root_ids.is_empty() {
return Ok(MoveCommitsStats::default()); return Ok(ComputedMoveCommits::empty());
} }
target_commit_ids = RevsetExpression::commits(root_ids.clone()) target_commit_ids = RevsetExpression::commits(root_ids.clone())
.descendants() .descendants()
.evaluate(mut_repo) .evaluate(repo)
.map_err(|err| err.into_backend_error())? .map_err(|err| err.into_backend_error())?
.iter() .iter()
.try_collect() .try_collect()
@ -479,7 +503,7 @@ pub fn move_commits(
connected_target_commits = target_commit_ids connected_target_commits = target_commit_ids
.iter() .iter()
.map(|id| mut_repo.store().get_commit(id)) .map(|id| repo.store().get_commit(id))
.try_collect()?; .try_collect()?;
// We don't have to compute the internal parents for the connected target set, // We don't have to compute the internal parents for the connected target set,
// since the connected target set is the same as the target set. // since the connected target set is the same as the target set.
@ -493,7 +517,7 @@ pub fn move_commits(
// ancestors which are not in the target set as parents. // ancestors which are not in the target set as parents.
let mut target_commits_external_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new(); let mut target_commits_external_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
for id in target_commit_ids.iter().rev() { for id in target_commit_ids.iter().rev() {
let commit = mut_repo.store().get_commit(id)?; let commit = repo.store().get_commit(id)?;
let mut new_parents = IndexSet::new(); let mut new_parents = IndexSet::new();
for old_parent in commit.parent_ids() { for old_parent in commit.parent_ids() {
if let Some(parents) = target_commits_external_parents.get(old_parent) { if let Some(parents) = target_commits_external_parents.get(old_parent) {
@ -534,10 +558,10 @@ pub fn move_commits(
&RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec()) &RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
.children(), .children(),
) )
.evaluate(mut_repo) .evaluate(repo)
.map_err(|err| err.into_backend_error())? .map_err(|err| err.into_backend_error())?
.iter() .iter()
.commits(mut_repo.store()) .commits(repo.store())
.try_collect() .try_collect()
.map_err(|err| err.into_backend_error())?; .map_err(|err| err.into_backend_error())?;
@ -582,14 +606,14 @@ pub fn move_commits(
if let Some(children) = target_commit_external_descendants.get(id) { if let Some(children) = target_commit_external_descendants.get(id) {
new_children.extend(children.iter().cloned()); new_children.extend(children.iter().cloned());
} else { } else {
new_children.push(mut_repo.store().get_commit(id)?); new_children.push(repo.store().get_commit(id)?);
} }
} }
new_children new_children
} else { } else {
loc.new_child_ids loc.new_child_ids
.iter() .iter()
.map(|id| mut_repo.store().get_commit(id)) .map(|id| repo.store().get_commit(id))
.try_collect()? .try_collect()?
}; };
@ -645,8 +669,8 @@ pub fn move_commits(
let mut roots = target_roots.iter().cloned().collect_vec(); let mut roots = target_roots.iter().cloned().collect_vec();
roots.extend(new_children.iter().ids().cloned()); roots.extend(new_children.iter().ids().cloned());
let descendants = mut_repo.find_descendants_for_rebase(roots.clone())?; let descendants = repo.find_descendants_for_rebase(roots.clone())?;
let commit_new_parents_map: HashMap<_, _> = descendants let commit_new_parents_map = descendants
.iter() .iter()
.map(|commit| { .map(|commit| {
let commit_id = commit.id(); let commit_id = commit.id();
@ -678,7 +702,7 @@ pub fn move_commits(
connected_target_commits_internal_parents.get(parent_id) { connected_target_commits_internal_parents.get(parent_id) {
new_parents.extend(parents.iter().cloned()); new_parents.extend(parents.iter().cloned());
} else if !new_children.iter().any(|new_child| { } else if !new_children.iter().any(|new_child| {
mut_repo.index().is_ancestor(new_child.id(), parent_id) }) { repo.index().is_ancestor(new_child.id(), parent_id) }) {
new_parents.push(parent_id.clone()); new_parents.push(parent_id.clone());
} }
} }
@ -708,6 +732,18 @@ pub fn move_commits(
}) })
.collect(); .collect();
Ok(ComputedMoveCommits {
target_commit_ids,
descendants,
commit_new_parents_map,
})
}
fn apply_move_commits(
mut_repo: &mut MutableRepo,
commits: ComputedMoveCommits,
options: &RebaseOptions,
) -> BackendResult<MoveCommitsStats> {
let mut num_rebased_targets = 0; let mut num_rebased_targets = 0;
let mut num_rebased_descendants = 0; let mut num_rebased_descendants = 0;
let mut num_skipped_rebases = 0; let mut num_skipped_rebases = 0;
@ -721,13 +757,13 @@ pub fn move_commits(
}; };
mut_repo.transform_commits( mut_repo.transform_commits(
descendants, commits.descendants,
&commit_new_parents_map, &commits.commit_new_parents_map,
&options.rewrite_refs, &options.rewrite_refs,
|rewriter| { |rewriter| {
let old_commit_id = rewriter.old_commit().id(); let old_commit_id = rewriter.old_commit().id();
if rewriter.parents_changed() { if rewriter.parents_changed() {
let is_target_commit = target_commit_ids.contains(old_commit_id); let is_target_commit = commits.target_commit_ids.contains(old_commit_id);
let rebased_commit = rebase_commit_with_options( let rebased_commit = rebase_commit_with_options(
rewriter, rewriter,
if is_target_commit { if is_target_commit {