Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make MutableRepo::parent_mapping private by moving code onto the type #3507

Merged
merged 6 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 171 additions & 3 deletions lib/src/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ use crate::operation::Operation;
use crate::refs::{
diff_named_ref_targets, diff_named_remote_refs, merge_ref_targets, merge_remote_refs,
};
use crate::revset::{RevsetExpression, RevsetIteratorExt};
use crate::rewrite::{DescendantRebaser, RebaseOptions};
use crate::settings::{RepoSettings, UserSettings};
use crate::signing::{SignInitError, Signer};
Expand Down Expand Up @@ -771,7 +772,6 @@ pub struct MutableRepo {
base_repo: Arc<ReadonlyRepo>,
index: Box<dyn MutableIndex>,
view: DirtyCell<View>,
// TODO: make these fields private again
// The commit identified by the key has been replaced by all the ones in the value.
// * Branches pointing to the old commit should be updated to the new commit, resulting in a
// conflict if there multiple new commits.
Expand All @@ -780,7 +780,7 @@ pub struct MutableRepo {
// * Working copies pointing to the old commit should be updated to the first of the new
// commits. However, if the type is `Abandoned`, a new working-copy commit should be created
// on top of all of the new commits instead.
pub(crate) parent_mapping: HashMap<CommitId, (RewriteType, Vec<CommitId>)>,
parent_mapping: HashMap<CommitId, (RewriteType, Vec<CommitId>)>,
}

impl MutableRepo {
Expand Down Expand Up @@ -981,6 +981,172 @@ impl MutableRepo {
}
}

/// Updates branches, working copies, and anonymous heads after rewriting
/// and/or abandoning commits.
pub fn update_rewritten_references(&mut self, settings: &UserSettings) -> BackendResult<()> {
self.update_all_references(settings)?;
self.update_heads();
Ok(())
}

fn update_all_references(&mut self, settings: &UserSettings) -> Result<(), BackendError> {
for (old_parent_id, (_, new_parent_ids)) in self.parent_mapping.clone() {
// Call `new_parents()` here since `parent_mapping` only contains direct
// 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);
self.update_references(settings, old_parent_id, new_parent_ids)?;
}
Ok(())
}

fn update_references(
&mut self,
settings: &UserSettings,
old_commit_id: CommitId,
new_commit_ids: Vec<CommitId>,
) -> Result<(), BackendError> {
// We arbitrarily pick a new working-copy commit among the candidates.
let abandoned_old_commit = matches!(
self.parent_mapping.get(&old_commit_id),
Some((RewriteType::Abandoned, _))
);
self.update_wc_commits(
settings,
&old_commit_id,
&new_commit_ids[0],
abandoned_old_commit,
)?;

// Build a map from commit to branches pointing to it, so we don't need to scan
// all branches each time we rebase a commit.
// TODO: We no longer need to do this now that we update branches for all
// commits at once.
let mut branches: HashMap<_, HashSet<_>> = HashMap::new();
for (branch_name, target) in self.view().local_branches() {
for commit in target.added_ids() {
branches
.entry(commit.clone())
.or_default()
.insert(branch_name.to_owned());
}
}

if let Some(branch_names) = branches.get(&old_commit_id).cloned() {
let mut branch_updates = vec![];
for branch_name in &branch_names {
let local_target = self.get_local_branch(branch_name);
for old_add in local_target.added_ids() {
if *old_add == old_commit_id {
branch_updates.push(branch_name.clone());
}
}
}

let old_target = RefTarget::normal(old_commit_id.clone());
assert!(!new_commit_ids.is_empty());
let new_target = RefTarget::from_legacy_form(
std::iter::repeat(old_commit_id).take(new_commit_ids.len() - 1),
new_commit_ids,
);
for branch_name in &branch_updates {
self.merge_local_branch(branch_name, &old_target, &new_target);
}
}

Ok(())
}

fn update_wc_commits(
martinvonz marked this conversation as resolved.
Show resolved Hide resolved
&mut self,
settings: &UserSettings,
old_commit_id: &CommitId,
new_commit_id: &CommitId,
abandoned_old_commit: bool,
) -> Result<(), BackendError> {
let workspaces_to_update = self.view().workspaces_for_wc_commit_id(old_commit_id);
if workspaces_to_update.is_empty() {
return Ok(());
}

let new_commit = self.store().get_commit(new_commit_id)?;
let new_wc_commit = if !abandoned_old_commit {
new_commit
} else {
self.new_commit(
settings,
vec![new_commit.id().clone()],
new_commit.tree_id().clone(),
)
.write()?
};
for workspace_id in workspaces_to_update.into_iter() {
self.edit(workspace_id, &new_wc_commit).unwrap();
}
Ok(())
}

fn update_heads(&mut self) {
let old_commits_expression =
RevsetExpression::commits(self.parent_mapping.keys().cloned().collect());
let heads_to_add_expression = old_commits_expression
.parents()
.minus(&old_commits_expression);
let heads_to_add = heads_to_add_expression
.evaluate_programmatic(self)
.unwrap()
.iter();

let mut view = self.view().store_view().clone();
for commit_id in self.parent_mapping.keys() {
view.head_ids.remove(commit_id);
}
view.head_ids.extend(heads_to_add);
self.set_view(view);
}

/// Find descendants of commits in `parent_mapping` and then return them in
/// an order they should be rebased in. The result is in reverse order
/// so the next value can be removed from the end.
fn find_descendants_to_rebase(&self) -> Vec<Commit> {
let store = self.store();
let old_commits_expression =
RevsetExpression::commits(self.parent_mapping.keys().cloned().collect());
let to_visit_expression = old_commits_expression
.descendants()
.minus(&old_commits_expression);
let to_visit_revset = to_visit_expression.evaluate_programmatic(self).unwrap();
let to_visit: Vec<_> = to_visit_revset.iter().commits(store).try_collect().unwrap();
drop(to_visit_revset);
let to_visit_set: HashSet<CommitId> =
to_visit.iter().map(|commit| commit.id().clone()).collect();
let mut visited = HashSet::new();
// Calculate an order where we rebase parents first, but if the parents were
// rewritten, make sure we rebase the rewritten parent first.
dag_walk::topo_order_reverse(
to_visit,
|commit| commit.id().clone(),
|commit| {
visited.insert(commit.id().clone());
let mut dependents = vec![];
for parent in commit.parents() {
if let Some((_, targets)) = self.parent_mapping.get(parent.id()) {
for target in targets {
if to_visit_set.contains(target) && !visited.contains(target) {
dependents.push(store.get_commit(target).unwrap());
}
}
}
if to_visit_set.contains(parent.id()) {
dependents.push(parent);
}
}
dependents
},
)
}

/// After the rebaser returned by this function is dropped,
/// self.parent_mapping needs to be cleared.
fn rebase_descendants_return_rebaser<'settings, 'repo>(
Expand All @@ -992,7 +1158,9 @@ impl MutableRepo {
// Optimization
return Ok(None);
}
let mut rebaser = DescendantRebaser::new(settings, self);

let to_visit = self.find_descendants_to_rebase();
let mut rebaser = DescendantRebaser::new(settings, self, to_visit);
*rebaser.mut_options() = options;
rebaser.rebase_all()?;
Ok(Some(rebaser))
Expand Down
Loading