rewrite: make merge_commit_trees() use index for finding common ancestors

The index is now always kept up to date and it has functionality for
finding common ancestors, so let's use it! This should make merging
commits a little faster if their common ancestor is far away (which is
rare). It's probably much more important that the index-based
algorithm is more correct. Also, it returns multiple common ancestors
in the criss-cross case, which lets us do a recursive merge like git
does. I'm leaving the recursive merge for later, though.
This commit is contained in:
Martin von Zweigbergk 2021-02-21 18:09:54 -08:00
parent bb94516175
commit 2a531832d6
4 changed files with 23 additions and 19 deletions

View file

@ -619,16 +619,16 @@ fn evolve_two_divergent_commits(
let rebased_tree2 = if commit2.parents() == new_parents {
commit2.tree()
} else {
let old_base_tree = merge_commit_trees(store, &commit2.parents());
let new_base_tree = merge_commit_trees(store, &new_parents);
let old_base_tree = merge_commit_trees(tx.as_repo_ref(), &commit2.parents());
let new_base_tree = merge_commit_trees(tx.as_repo_ref(), &new_parents);
let tree_id = merge_trees(&new_base_tree, &old_base_tree, &commit2.tree()).unwrap();
store.get_tree(&DirRepoPath::root(), &tree_id).unwrap()
};
let rebased_predecessor_tree = if common_predecessor.parents() == new_parents {
common_predecessor.tree()
} else {
let old_base_tree = merge_commit_trees(store, &common_predecessor.parents());
let new_base_tree = merge_commit_trees(store, &new_parents);
let old_base_tree = merge_commit_trees(tx.as_repo_ref(), &common_predecessor.parents());
let new_base_tree = merge_commit_trees(tx.as_repo_ref(), &new_parents);
let tree_id =
merge_trees(&new_base_tree, &old_base_tree, &common_predecessor.tree()).unwrap();
store.get_tree(&DirRepoPath::root(), &tree_id).unwrap()

View file

@ -104,10 +104,10 @@ impl<'a> IndexRef<'a> {
}
}
pub fn common_ancestors(&self, ids1: &[CommitId], ids2: &[CommitId]) -> Vec<CommitId> {
pub fn common_ancestors(&self, set1: &[CommitId], set2: &[CommitId]) -> Vec<CommitId> {
match self {
IndexRef::Readonly(index) => index.common_ancestors(ids1, ids2),
IndexRef::Mutable(index) => index.common_ancestors(ids1, ids2),
IndexRef::Readonly(index) => index.common_ancestors(set1, set2),
IndexRef::Mutable(index) => index.common_ancestors(set1, set2),
}
}

View file

@ -14,23 +14,27 @@
use crate::commit::Commit;
use crate::commit_builder::CommitBuilder;
use crate::dag_walk::common_ancestor;
use crate::repo::RepoRef;
use crate::repo_path::DirRepoPath;
use crate::settings::UserSettings;
use crate::store_wrapper::StoreWrapper;
use crate::transaction::Transaction;
use crate::tree::Tree;
use crate::trees::merge_trees;
pub fn merge_commit_trees(store: &StoreWrapper, commits: &[Commit]) -> Tree {
pub fn merge_commit_trees(repo: RepoRef, commits: &[Commit]) -> Tree {
let store = repo.store();
if commits.is_empty() {
store
.get_tree(&DirRepoPath::root(), store.empty_tree_id())
.unwrap()
} else {
let index = repo.index();
let mut new_tree = commits[0].tree();
let commit_ids: Vec<_> = commits.iter().map(|commit| commit.id().clone()).collect();
for (i, other_commit) in commits.iter().enumerate().skip(1) {
let ancestor = common_ancestor(&commits[0..i], vec![other_commit]);
let ancestors = index.common_ancestors(&commit_ids[0..i], &[commit_ids[i].clone()]);
// TODO: Do recursive merge here instead of using just the first ancestor.
let ancestor = store.get_commit(&ancestors[0]).unwrap();
let new_tree_id =
merge_trees(&new_tree, &ancestor.tree(), &other_commit.tree()).unwrap();
new_tree = store.get_tree(&DirRepoPath::root(), &new_tree_id).unwrap();
@ -46,8 +50,8 @@ pub fn rebase_commit(
new_parents: &[Commit],
) -> Commit {
let store = tx.store();
let old_base_tree = merge_commit_trees(store, &old_commit.parents());
let new_base_tree = merge_commit_trees(store, &new_parents);
let old_base_tree = merge_commit_trees(tx.as_repo_ref(), &old_commit.parents());
let new_base_tree = merge_commit_trees(tx.as_repo_ref(), &new_parents);
// TODO: pass in labels for the merge parts
let new_tree_id = merge_trees(&new_base_tree, &old_base_tree, &old_commit.tree()).unwrap();
let new_parent_ids = new_parents
@ -67,8 +71,8 @@ pub fn back_out_commit(
new_parents: &[Commit],
) -> Commit {
let store = tx.store();
let old_base_tree = merge_commit_trees(store, &old_commit.parents());
let new_base_tree = merge_commit_trees(store, &new_parents);
let old_base_tree = merge_commit_trees(tx.as_repo_ref(), &old_commit.parents());
let new_base_tree = merge_commit_trees(tx.as_repo_ref(), &new_parents);
// TODO: pass in labels for the merge parts
let new_tree_id = merge_trees(&new_base_tree, &old_commit.tree(), &old_base_tree).unwrap();
let new_parent_ids = new_parents

View file

@ -769,7 +769,7 @@ fn cmd_diff(
sub_matches.value_of("revision").unwrap_or("@"),
)?;
let parents = commit.parents();
from_tree = merge_commit_trees(repo.store(), &parents);
from_tree = merge_commit_trees(repo.as_repo_ref(), &parents);
to_tree = commit.tree()
}
if sub_matches.is_present("summary") {
@ -1484,7 +1484,7 @@ fn cmd_edit(
let owned_wc = repo.working_copy().clone();
let mut_repo = Arc::get_mut(&mut repo).unwrap();
let commit = resolve_revision_arg(ui, mut_repo, sub_matches)?;
let base_tree = merge_commit_trees(repo.store(), &commit.parents());
let base_tree = merge_commit_trees(repo.as_repo_ref(), &commit.parents());
let tree_id = crate::diff_edit::edit_diff(&base_tree, &commit.tree())?;
if &tree_id == commit.tree().id() {
ui.write("Nothing changed.\n");
@ -1516,7 +1516,7 @@ fn cmd_split(
let owned_wc = repo.working_copy().clone();
let mut_repo = Arc::get_mut(&mut repo).unwrap();
let commit = resolve_revision_arg(ui, mut_repo, sub_matches)?;
let base_tree = merge_commit_trees(repo.store(), &commit.parents());
let base_tree = merge_commit_trees(repo.as_repo_ref(), &commit.parents());
let tree_id = crate::diff_edit::edit_diff(&base_tree, &commit.tree())?;
if &tree_id == commit.tree().id() {
ui.write("Nothing changed.\n");
@ -1579,7 +1579,7 @@ fn cmd_merge(
} else {
description = edit_description(&repo, "");
}
let merged_tree = merge_commit_trees(repo.store(), &commits);
let merged_tree = merge_commit_trees(repo.as_repo_ref(), &commits);
let mut tx = repo.start_transaction("merge commits");
CommitBuilder::for_new_commit(ui.settings(), repo.store(), merged_tree.id().clone())
.set_parents(parent_ids)