unapply_ownership uses gitoxide merging.

Note that this at best is done for getting better merge results, it's not significant
for performance.
This commit is contained in:
Sebastian Thiel 2024-11-03 17:13:02 +01:00
parent 6a80637864
commit 01216278c8
No known key found for this signature in database
GPG Key ID: 9CB5EE7895E8268B
3 changed files with 44 additions and 21 deletions

View File

@ -53,9 +53,7 @@ pub(crate) fn get_workspace_head(ctx: &CommandContext) -> Result<git2::Oid> {
workspace_tree = repo.find_commit(merge_base)?.tree()?;
} else {
let gix_repo = ctx.gix_repository_for_merging()?;
let mut merge_options_fail_fast = gix_repo.tree_merge_options()?;
let conflict_kind = gix::merge::tree::UnresolvedConflict::Renames;
merge_options_fail_fast.fail_on_conflict = Some(conflict_kind);
let (merge_options_fail_fast, conflict_kind) = gix_repo.merge_options_fail_fast()?;
let merge_tree_id = git2_to_gix_object_id(repo.find_commit(target.sha)?.tree_id());
for branch in virtual_branches.iter_mut() {
let branch_head = repo.find_commit(branch.head())?;

View File

@ -25,7 +25,7 @@ use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_reference::{normalize_branch_name, Refname, RemoteRefname};
use gitbutler_repo::{
rebase::{cherry_rebase, cherry_rebase_group},
LogUntil, RepositoryExt,
GixRepositoryExt, LogUntil, RepositoryExt,
};
use gitbutler_repo_actions::RepoActionsExt;
use gitbutler_stack::{
@ -180,25 +180,37 @@ pub fn unapply_ownership(
.find_commit(workspace_commit_id)
.context("failed to find target commit")?;
let base_tree = target_commit.tree().context("failed to get target tree")?;
let final_tree = applied_statuses.into_iter().fold(
target_commit.tree().context("failed to get target tree"),
|final_tree, status| {
let final_tree = final_tree?;
let base_tree_id = git2_to_gix_object_id(target_commit.tree_id());
let gix_repo = ctx.gix_repository_for_merging()?;
let (merge_options_fail_fast, conflict_kind) = gix_repo.merge_options_fail_fast()?;
let final_tree_id = applied_statuses.into_iter().try_fold(
git2_to_gix_object_id(target_commit.tree_id()),
|final_tree_id, status| -> Result<_> {
let files = status
.1
.into_iter()
.map(|file| (file.path, file.hunks))
.collect::<Vec<(PathBuf, Vec<VirtualBranchHunk>)>>();
let tree_oid = gitbutler_diff::write::hunks_onto_oid(ctx, workspace_commit_id, files)?;
let branch_tree = repo.find_tree(tree_oid)?;
let mut result = repo.merge_trees(&base_tree, &final_tree, &branch_tree, None)?;
let final_tree_oid = result.write_tree_to(ctx.repository())?;
repo.find_tree(final_tree_oid)
.context("failed to find tree")
let branch_tree_id =
gitbutler_diff::write::hunks_onto_oid(ctx, workspace_commit_id, files)?;
let mut merge = gix_repo.merge_trees(
base_tree_id,
final_tree_id,
git2_to_gix_object_id(branch_tree_id),
gix_repo.default_merge_labels(),
merge_options_fail_fast.clone(),
)?;
if merge.has_unresolved_conflicts(conflict_kind) {
bail!("Tree has conflicts after merge")
}
merge
.tree
.write(|tree| gix_repo.write(tree))
.map_err(|err| anyhow!("Could not write merged tree: {err}"))
},
)?;
let final_tree = repo.find_tree(gix_to_git2_oid(final_tree_id))?;
let final_tree_oid = gitbutler_diff::write::hunks_onto_tree(ctx, &final_tree, diff, true)?;
let final_tree = repo
.find_tree(final_tree_oid)
@ -1035,9 +1047,7 @@ impl IsCommitIntegrated<'_, '_, '_> {
}
// try to merge our tree into the upstream tree
let mut merge_options = self.gix_repo.tree_merge_options()?;
let conflict_kind = gix::merge::tree::UnresolvedConflict::Renames;
merge_options.fail_on_conflict = Some(conflict_kind);
let (merge_options, conflict_kind) = self.gix_repo.merge_options_fail_fast()?;
let mut merge_output = self
.gix_repo
.merge_trees(

View File

@ -17,6 +17,7 @@ use gitbutler_oxidize::{
};
use gitbutler_reference::{Refname, RemoteRefname};
use gix::fs::is_executable;
use gix::merge::tree::{Options, UnresolvedConflict};
use gix::objs::WriteTo;
use tracing::instrument;
@ -731,6 +732,15 @@ pub trait GixRepositoryExt: Sized {
other: Some("theirs".into()),
}
}
/// Return options suitable for merging so that the merge stops immediately after the first conflict.
/// It also returns the conflict kind to use when checking for unresolved conflicts.
fn merge_options_fail_fast(
&self,
) -> Result<(
gix::merge::tree::Options,
gix::merge::tree::UnresolvedConflict,
)>;
}
impl GixRepositoryExt for gix::Repository {
@ -759,9 +769,7 @@ impl GixRepositoryExt for gix::Repository {
our_tree: gix::ObjectId,
their_tree: gix::ObjectId,
) -> Result<bool> {
let mut options = self.tree_merge_options()?;
let conflict_kind = gix::merge::tree::UnresolvedConflict::Renames;
options.fail_on_conflict = Some(conflict_kind);
let (options, conflict_kind) = self.merge_options_fail_fast()?;
let merge_outcome = self
.merge_trees(
ancestor_tree,
@ -773,6 +781,13 @@ impl GixRepositoryExt for gix::Repository {
.context("failed to merge trees")?;
Ok(!merge_outcome.has_unresolved_conflicts(conflict_kind))
}
fn merge_options_fail_fast(&self) -> Result<(Options, UnresolvedConflict)> {
let mut options = self.tree_merge_options()?;
let conflict_kind = gix::merge::tree::UnresolvedConflict::Renames;
options.fail_on_conflict = Some(conflict_kind);
Ok((options, conflict_kind))
}
}
type OidFilter = dyn Fn(&git2::Commit) -> Result<bool>;