#![allow(missing_docs)]
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use futures::StreamExt;
use itertools::Itertools;
use pollster::FutureExt;
use tracing::instrument;
use crate::backend::{BackendError, BackendResult, CommitId, MergedTreeId};
use crate::commit::Commit;
use crate::commit_builder::CommitBuilder;
use crate::index::Index;
use crate::matchers::{Matcher, Visit};
use crate::merged_tree::{MergedTree, MergedTreeBuilder};
use crate::object_id::ObjectId;
use crate::repo::{MutableRepo, Repo};
use crate::repo_path::RepoPath;
use crate::settings::UserSettings;
use crate::store::Store;
#[instrument(skip(repo))]
pub fn merge_commit_trees(repo: &dyn Repo, commits: &[Commit]) -> BackendResult<MergedTree> {
merge_commit_trees_without_repo(repo.store(), repo.index(), commits)
}
#[instrument(skip(index))]
pub fn merge_commit_trees_without_repo(
store: &Arc<Store>,
index: &dyn Index,
commits: &[Commit],
) -> BackendResult<MergedTree> {
if commits.is_empty() {
Ok(store.get_root_tree(&store.empty_merged_tree_id())?)
} else {
let mut new_tree = commits[0].tree()?;
let commit_ids = commits
.iter()
.map(|commit| commit.id().clone())
.collect_vec();
for (i, other_commit) in commits.iter().enumerate().skip(1) {
let ancestor_ids = index.common_ancestors(&commit_ids[0..i], &[commit_ids[i].clone()]);
let ancestors: Vec<_> = ancestor_ids
.iter()
.map(|id| store.get_commit(id))
.try_collect()?;
let ancestor_tree = merge_commit_trees_without_repo(store, index, &ancestors)?;
let other_tree = other_commit.tree()?;
new_tree = new_tree.merge(&ancestor_tree, &other_tree)?;
}
Ok(new_tree)
}
}
pub fn restore_tree(
source: &MergedTree,
destination: &MergedTree,
matcher: &dyn Matcher,
) -> BackendResult<MergedTreeId> {
if matcher.visit(RepoPath::root()) == Visit::AllRecursively {
Ok(source.id())
} else {
let mut tree_builder = MergedTreeBuilder::new(destination.id().clone());
async {
let mut diff_stream = source.diff_stream(destination, matcher);
while let Some((repo_path, diff)) = diff_stream.next().await {
let (source_value, _destination_value) = diff?;
tree_builder.set_or_remove(repo_path, source_value);
}
Ok::<(), BackendError>(())
}
.block_on()?;
tree_builder.write_tree(destination.store())
}
}
pub fn rebase_commit(
settings: &UserSettings,
mut_repo: &mut MutableRepo,
old_commit: Commit,
new_parents: Vec<CommitId>,
) -> BackendResult<Commit> {
let rewriter = CommitRewriter::new(mut_repo, old_commit, new_parents);
let builder = rewriter.rebase(settings)?;
builder.write()
}
pub struct CommitRewriter<'repo> {
mut_repo: &'repo mut MutableRepo,
old_commit: Commit,
new_parents: Vec<CommitId>,
}
impl<'repo> CommitRewriter<'repo> {
pub fn new(
mut_repo: &'repo mut MutableRepo,
old_commit: Commit,
new_parents: Vec<CommitId>,
) -> Self {
Self {
mut_repo,
old_commit,
new_parents,
}
}
pub fn mut_repo(&mut self) -> &mut MutableRepo {
self.mut_repo
}
pub fn old_commit(&self) -> &Commit {
&self.old_commit
}
pub fn new_parents(&self) -> &[CommitId] {
&self.new_parents
}
pub fn set_new_parents(&mut self, new_parents: Vec<CommitId>) {
self.new_parents = new_parents;
}
pub fn set_new_rewritten_parents(&mut self, unrewritten_parents: Vec<CommitId>) {
self.new_parents = self.mut_repo.new_parents(unrewritten_parents);
}
pub fn replace_parent<'a>(
&mut self,
old_parent: &CommitId,
new_parents: impl IntoIterator<Item = &'a CommitId>,
) {
if let Some(i) = self.new_parents.iter().position(|p| p == old_parent) {
self.new_parents
.splice(i..i + 1, new_parents.into_iter().cloned());
let mut unique = HashSet::new();
self.new_parents.retain(|p| unique.insert(p.clone()));
}
}
pub fn parents_changed(&self) -> bool {
self.new_parents != self.old_commit.parent_ids()
}
pub fn simplify_ancestor_merge(&mut self) {
let head_set: HashSet<_> = self
.mut_repo
.index()
.heads(&mut self.new_parents.iter())
.into_iter()
.collect();
self.new_parents.retain(|parent| head_set.contains(parent));
}
pub fn abandon(self) {
let old_commit_id = self.old_commit.id().clone();
let new_parents = self.new_parents;
self.mut_repo
.record_abandoned_commit_with_parents(old_commit_id, new_parents);
}
pub fn rebase_with_empty_behavior(
self,
settings: &UserSettings,
empty: EmptyBehaviour,
) -> BackendResult<Option<CommitBuilder<'repo>>> {
let old_parents: Vec<_> = self.old_commit.parents().try_collect()?;
let old_parent_trees = old_parents
.iter()
.map(|parent| parent.tree_id().clone())
.collect_vec();
let new_parents: Vec<_> = self
.new_parents
.iter()
.map(|new_parent_id| self.mut_repo.store().get_commit(new_parent_id))
.try_collect()?;
let new_parent_trees = new_parents
.iter()
.map(|parent| parent.tree_id().clone())
.collect_vec();
let (was_empty, new_tree_id) = if new_parent_trees == old_parent_trees {
(
true,
self.old_commit.tree_id().clone(),
)
} else {
let old_base_tree = merge_commit_trees(self.mut_repo, &old_parents)?;
let new_base_tree = merge_commit_trees(self.mut_repo, &new_parents)?;
let old_tree = self.old_commit.tree()?;
(
old_base_tree.id() == *self.old_commit.tree_id(),
new_base_tree.merge(&old_base_tree, &old_tree)?.id(),
)
};
if let [parent] = &new_parents[..] {
let should_abandon = match empty {
EmptyBehaviour::Keep => false,
EmptyBehaviour::AbandonNewlyEmpty => *parent.tree_id() == new_tree_id && !was_empty,
EmptyBehaviour::AbandonAllEmpty => *parent.tree_id() == new_tree_id,
};
if should_abandon {
self.abandon();
return Ok(None);
}
}
let builder = self
.mut_repo
.rewrite_commit(settings, &self.old_commit)
.set_parents(self.new_parents)
.set_tree_id(new_tree_id);
Ok(Some(builder))
}
pub fn rebase(self, settings: &UserSettings) -> BackendResult<CommitBuilder<'repo>> {
let builder = self.rebase_with_empty_behavior(settings, EmptyBehaviour::Keep)?;
Ok(builder.unwrap())
}
pub fn reparent(self, settings: &UserSettings) -> BackendResult<CommitBuilder<'repo>> {
Ok(self
.mut_repo
.rewrite_commit(settings, &self.old_commit)
.set_parents(self.new_parents))
}
}
pub enum RebasedCommit {
Rewritten(Commit),
Abandoned { parent: Commit },
}
pub fn rebase_commit_with_options(
settings: &UserSettings,
mut rewriter: CommitRewriter<'_>,
options: &RebaseOptions,
) -> BackendResult<RebasedCommit> {
if options.simplify_ancestor_merge {
rewriter.simplify_ancestor_merge();
}
let store = rewriter.mut_repo().store().clone();
let single_parent = match &rewriter.new_parents[..] {
[parent] => Some(store.get_commit(parent)?),
_ => None,
};
let new_parents = rewriter.new_parents.clone();
if let Some(builder) = rewriter.rebase_with_empty_behavior(settings, options.empty)? {
let new_commit = builder.write()?;
Ok(RebasedCommit::Rewritten(new_commit))
} else {
assert_eq!(new_parents.len(), 1);
Ok(RebasedCommit::Abandoned {
parent: single_parent.unwrap(),
})
}
}
pub fn rebase_to_dest_parent(
repo: &dyn Repo,
source: &Commit,
destination: &Commit,
) -> BackendResult<MergedTree> {
if source.parent_ids() == destination.parent_ids() {
Ok(source.tree()?)
} else {
let destination_parent_tree = destination.parent_tree(repo)?;
let source_parent_tree = source.parent_tree(repo)?;
let source_tree = source.tree()?;
let rebased_tree = destination_parent_tree.merge(&source_parent_tree, &source_tree)?;
Ok(rebased_tree)
}
}
pub fn back_out_commit(
settings: &UserSettings,
mut_repo: &mut MutableRepo,
old_commit: &Commit,
new_parents: &[Commit],
) -> BackendResult<Commit> {
let old_base_tree = old_commit.parent_tree(mut_repo)?;
let new_base_tree = merge_commit_trees(mut_repo, new_parents)?;
let old_tree = old_commit.tree()?;
let new_tree = new_base_tree.merge(&old_tree, &old_base_tree)?;
let new_parent_ids = new_parents
.iter()
.map(|commit| commit.id().clone())
.collect();
mut_repo
.new_commit(settings, new_parent_ids, new_tree.id())
.set_description(format!("backout of commit {}", &old_commit.id().hex()))
.write()
}
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)]
pub enum EmptyBehaviour {
#[default]
Keep,
AbandonNewlyEmpty,
AbandonAllEmpty,
}
#[derive(Clone, Default, PartialEq, Eq, Debug)]
pub struct RebaseOptions {
pub empty: EmptyBehaviour,
pub simplify_ancestor_merge: bool,
}
pub(crate) struct DescendantRebaser<'settings, 'repo> {
settings: &'settings UserSettings,
mut_repo: &'repo mut MutableRepo,
to_visit: Vec<Commit>,
rebased: HashMap<CommitId, CommitId>,
options: RebaseOptions,
}
impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
pub fn new(
settings: &'settings UserSettings,
mut_repo: &'repo mut MutableRepo,
to_visit: Vec<Commit>,
) -> DescendantRebaser<'settings, 'repo> {
DescendantRebaser {
settings,
mut_repo,
to_visit,
rebased: Default::default(),
options: Default::default(),
}
}
pub fn mut_options(&mut self) -> &mut RebaseOptions {
&mut self.options
}
pub fn into_map(self) -> HashMap<CommitId, CommitId> {
self.rebased
}
fn rebase_one(&mut self, old_commit: Commit) -> BackendResult<()> {
let old_commit_id = old_commit.id().clone();
let old_parent_ids = old_commit.parent_ids();
let new_parent_ids = self.mut_repo.new_parents(old_parent_ids.to_vec());
let rewriter = CommitRewriter::new(self.mut_repo, old_commit, new_parent_ids);
if !rewriter.parents_changed() {
return Ok(());
}
let rebased_commit: RebasedCommit =
rebase_commit_with_options(self.settings, rewriter, &self.options)?;
let new_commit = match rebased_commit {
RebasedCommit::Rewritten(new_commit) => new_commit,
RebasedCommit::Abandoned { parent } => parent,
};
self.rebased
.insert(old_commit_id.clone(), new_commit.id().clone());
Ok(())
}
pub fn rebase_all(&mut self) -> BackendResult<()> {
while let Some(old_commit) = self.to_visit.pop() {
self.rebase_one(old_commit)?;
}
self.mut_repo.update_rewritten_references(self.settings)
}
}