Skip to content

Commit

Permalink
Merge pull request #5416 from gitbutlerapp/tree-merge
Browse files Browse the repository at this point in the history
Replace `merge-tree` in more places
  • Loading branch information
krlvi authored Nov 5, 2024
2 parents 94109d0 + 1031de1 commit 7fe5d0c
Show file tree
Hide file tree
Showing 23 changed files with 704 additions and 435 deletions.
103 changes: 52 additions & 51 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ resolver = "2"
[workspace.dependencies]
bstr = "1.10.0"
# Add the `tracing` or `tracing-detail` features to see more of gitoxide in the logs. Useful to see which programs it invokes.
gix = { git = "https://github.com/Byron/gitoxide", rev = "3fb989be21c739bbfeac93953c1685e7c6cd2106", default-features = false, features = [
gix = { git = "https://github.com/Byron/gitoxide", rev = "a8765330fc16997dee275866b18a128dec1c5d55", default-features = false, features = [
] }
git2 = { version = "0.19.0", features = [
"vendored-openssl",
Expand Down
2 changes: 1 addition & 1 deletion crates/gitbutler-branch-actions/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ publish = false
tracing.workspace = true
anyhow = "1.0.92"
git2.workspace = true
gix = { workspace = true, features = ["blob-diff", "revision", "blob-merge"] }
gix = { workspace = true, features = ["blob-diff", "revision", "merge"] }
tokio.workspace = true
gitbutler-oplog.workspace = true
gitbutler-repo.workspace = true
Expand Down
75 changes: 35 additions & 40 deletions crates/gitbutler-branch-actions/src/base.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
use std::{path::Path, time};

use anyhow::{anyhow, Context, Result};
use crate::{
conflicts::RepoConflictsExt,
hunk::VirtualBranchHunk,
integration::update_workspace_commit,
remote::{commit_to_remote_commit, RemoteCommit},
VirtualBranchesExt,
};
use anyhow::{anyhow, bail, Context, Result};
use gitbutler_branch::GITBUTLER_WORKSPACE_REFERENCE;
use gitbutler_command_context::CommandContext;
use gitbutler_error::error::Marker;
use gitbutler_oxidize::{git2_to_gix_object_id, gix_to_git2_oid};
use gitbutler_project::FetchResult;
use gitbutler_reference::{Refname, RemoteRefname};
use gitbutler_repo::{LogUntil, RepositoryExt};
use gitbutler_repo::{GixRepositoryExt, LogUntil, RepositoryExt};
use gitbutler_repo_actions::RepoActionsExt;
use gitbutler_stack::{BranchOwnershipClaims, Stack, Target, VirtualBranchesHandle};
use serde::Serialize;

use crate::{
conflicts::RepoConflictsExt,
hunk::VirtualBranchHunk,
integration::update_workspace_commit,
remote::{commit_to_remote_commit, RemoteCommit},
VirtualBranchesExt,
};

#[derive(Debug, Serialize, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct BaseBranch {
Expand Down Expand Up @@ -50,8 +50,8 @@ pub(crate) fn get_base_branch_data(ctx: &CommandContext) -> Result<BaseBranch> {
}

fn go_back_to_integration(ctx: &CommandContext, default_target: &Target) -> Result<BaseBranch> {
let statuses = ctx
.repository()
let repo = ctx.repository();
let statuses = repo
.statuses(Some(
git2::StatusOptions::new()
.show(git2::StatusShow::IndexAndWorkdir)
Expand All @@ -67,41 +67,36 @@ fn go_back_to_integration(ctx: &CommandContext, default_target: &Target) -> Resu
.list_branches_in_workspace()
.context("failed to read virtual branches")?;

let target_commit = ctx
.repository()
let target_commit = repo
.find_commit(default_target.sha)
.context("failed to find target commit")?;

let base_tree = target_commit
.tree()
.context("failed to get base tree from commit")?;
let mut final_tree = target_commit
.tree()
.context("failed to get base tree from commit")?;
let base_tree = git2_to_gix_object_id(target_commit.tree_id());
let mut final_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()?;
for branch in &virtual_branches {
// merge this branches tree with our tree
let branch_head = ctx
.repository()
.find_commit(branch.head())
.context("failed to find branch head")?;
let branch_tree = branch_head
.tree()
.context("failed to get branch head tree")?;
let mut result = ctx
.repository()
.merge_trees(&base_tree, &final_tree, &branch_tree, None)
.context("failed to merge")?;
let final_tree_oid = result
.write_tree_to(ctx.repository())
.context("failed to write tree")?;
final_tree = ctx
.repository()
.find_tree(final_tree_oid)
.context("failed to find written tree")?;
let branch_tree_id = git2_to_gix_object_id(
repo.find_commit(branch.head())
.context("failed to find branch head")?
.tree_id(),
);
let mut merge = gix_repo.merge_trees(
base_tree,
final_tree_id,
branch_tree_id,
gix_repo.default_merge_labels(),
merge_options_fail_fast.clone(),
)?;
if merge.has_unresolved_conflicts(conflict_kind) {
bail!("Merge failed with conflicts");
}
final_tree_id = merge.tree.write()?.detach();
}

ctx.repository()
.checkout_tree_builder(&final_tree)
let final_tree = repo.find_tree(gix_to_git2_oid(final_tree_id))?;
repo.checkout_tree_builder(&final_tree)
.force()
.checkout()
.context("failed to checkout tree")?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use gitbutler_error::error::Marker;
use gitbutler_oplog::SnapshotExt;
use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_reference::{Refname, RemoteRefname};
use gitbutler_repo::GixRepositoryExt;
use gitbutler_repo::{
rebase::{cherry_rebase_group, gitbutler_merge_commits},
LogUntil, RepositoryExt,
Expand Down Expand Up @@ -301,29 +302,27 @@ impl BranchManager<'_> {
))?;

// Branch is out of date, merge or rebase it
let merge_base_tree = repo
let merge_base_tree_id = repo
.find_commit(merge_base)
.context(format!("failed to find merge base commit {}", merge_base))?
.tree()
.context("failed to find merge base tree")?;

let branch_tree = repo
.find_tree(branch.tree)
.context("failed to find branch tree")?;
.context("failed to find merge base tree")?
.id();
let branch_tree_id = branch.tree;

// We don't support having two branches applied that conflict with each other
{
let uncommited_changes_tree = repo.create_wd_tree()?;
let branch_merged_with_other_applied_branches = repo
.merge_trees(
&merge_base_tree,
&branch_tree,
&uncommited_changes_tree,
None,
let uncommited_changes_tree_id = repo.create_wd_tree()?.id();
let gix_repo = self.ctx.gix_repository_for_merging_non_persisting()?;
let merges_cleanly = gix_repo
.merges_cleanly_compat(
merge_base_tree_id,
branch_tree_id,
uncommited_changes_tree_id,
)
.context("failed to merge trees")?;

if branch_merged_with_other_applied_branches.has_conflicts() {
if !merges_cleanly {
for branch in vb_state
.list_branches_in_workspace()?
.iter()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ use git2::Commit;
use gitbutler_branch::BranchExt;
use gitbutler_commit::commit_headers::CommitHeadersV2;
use gitbutler_oplog::SnapshotExt;
use gitbutler_oxidize::git2_to_gix_object_id;
use gitbutler_oxidize::gix_to_git2_oid;
use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_reference::{normalize_branch_name, ReferenceName, Refname};
use gitbutler_repo::GixRepositoryExt;
use gitbutler_repo::RepositoryExt;
use gitbutler_repo::SignaturePurpose;
use gitbutler_repo_actions::RepoActionsExt;
Expand Down Expand Up @@ -79,7 +82,10 @@ impl BranchManager<'_> {

let repo = self.ctx.repository();

let base_tree = target_commit.tree().context("failed to get target tree")?;
let base_tree_id = target_commit
.tree()
.context("failed to get target tree")?
.id();

let applied_statuses = get_applied_status(self.ctx, None)
.context("failed to get status by branch")?
Expand All @@ -98,13 +104,14 @@ impl BranchManager<'_> {
num_branches = applied_statuses.len() - 1
)
.entered();
applied_statuses
let gix_repo = self.ctx.gix_repository()?;
let merge_options = gix_repo.tree_merge_options()?;
let final_tree_id = applied_statuses
.into_iter()
.filter(|(branch, _)| branch.id != branch_id)
.fold(
target_commit.tree().context("failed to get target tree"),
|final_tree, status| {
let final_tree = final_tree?;
.try_fold(
git2_to_gix_object_id(target_commit.tree_id()),
|final_tree_id, status| -> Result<_> {
let branch = status.0;
let files = status
.1
Expand All @@ -113,14 +120,18 @@ impl BranchManager<'_> {
.collect::<Vec<(PathBuf, Vec<VirtualBranchHunk>)>>();
let tree_oid =
gitbutler_diff::write::hunks_onto_oid(self.ctx, branch.head(), 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(repo)?;
repo.find_tree(final_tree_oid)
.context("failed to find tree")
let mut merge = gix_repo.merge_trees(
git2_to_gix_object_id(base_tree_id),
final_tree_id,
git2_to_gix_object_id(tree_oid),
gix_repo.default_merge_labels(),
merge_options.clone(),
)?;
let final_tree_id = merge.tree.write()?.detach();
Ok(final_tree_id)
},
)?
)?;
repo.find_tree(gix_to_git2_oid(final_tree_id))?
};

let _span = tracing::debug_span!("checkout final tree").entered();
Expand Down
35 changes: 22 additions & 13 deletions crates/gitbutler-branch-actions/src/branch_trees.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
use crate::VirtualBranchesExt as _;
use anyhow::{bail, Result};
use gitbutler_cherry_pick::RepositoryExt;
use gitbutler_command_context::CommandContext;
use gitbutler_commit::commit_ext::CommitExt as _;
use gitbutler_oxidize::{git2_to_gix_object_id, gix_to_git2_oid};
use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_repo::rebase::cherry_rebase_group;
use gitbutler_repo::RepositoryExt as _;
use gitbutler_repo::{GixRepositoryExt, RepositoryExt as _};
use gitbutler_stack::Stack;

use crate::VirtualBranchesExt as _;
use tracing::instrument;

/// Checks out the combined trees of all branches in the workspace.
///
/// This function will fail if the applied branches conflict with each other.
#[instrument(level = tracing::Level::DEBUG, skip(ctx, _perm), err(Debug))]
pub fn checkout_branch_trees<'a>(
ctx: &'a CommandContext,
_perm: &mut WorktreeWritePermission,
Expand All @@ -38,23 +40,30 @@ pub fn checkout_branch_trees<'a>(
let merge_base = repository
.merge_base_octopussy(&branches.iter().map(|b| b.head()).collect::<Vec<_>>())?;

let merge_base_tree = repository.find_commit(merge_base)?.tree()?;

let mut final_tree = merge_base_tree.clone();
let gix_repo = ctx.gix_repository_for_merging()?;
let merge_base_tree_id =
git2_to_gix_object_id(repository.find_commit(merge_base)?.tree_id());
let mut final_tree_id = merge_base_tree_id;

let (merge_options_fail_fast, conflict_kind) = gix_repo.merge_options_fail_fast()?;
for branch in branches {
let theirs = repository.find_tree(branch.tree)?;
let mut merge_index =
repository.merge_trees(&merge_base_tree, &final_tree, &theirs, None)?;

if merge_index.has_conflicts() {
let their_tree_id = git2_to_gix_object_id(branch.tree);
let mut merge = gix_repo.merge_trees(
merge_base_tree_id,
final_tree_id,
their_tree_id,
gix_repo.default_merge_labels(),
merge_options_fail_fast.clone(),
)?;

if merge.has_unresolved_conflicts(conflict_kind) {
bail!("There appears to be conflicts between the virtual branches");
};

let tree_oid = merge_index.write_tree_to(repository)?;
final_tree = repository.find_tree(tree_oid)?;
final_tree_id = merge.tree.write()?.detach();
}

let final_tree = repository.find_tree(gix_to_git2_oid(final_tree_id))?;
repository
.checkout_tree_builder(&final_tree)
.force()
Expand Down
28 changes: 20 additions & 8 deletions crates/gitbutler-branch-actions/src/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use gitbutler_command_context::CommandContext;
use gitbutler_commit::commit_ext::CommitExt;
use gitbutler_error::error::Marker;
use gitbutler_operating_modes::OPEN_WORKSPACE_REFS;
use gitbutler_oxidize::{git2_to_gix_object_id, gix_to_git2_oid};
use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_repo::SignaturePurpose;
use gitbutler_repo::{GixRepositoryExt, SignaturePurpose};
use gitbutler_repo::{LogUntil, RepositoryExt};
use gitbutler_stack::{Stack, VirtualBranchesHandle};
use tracing::instrument;
Expand Down Expand Up @@ -41,6 +42,7 @@ pub(crate) fn get_workspace_head(ctx: &CommandContext) -> Result<git2::Oid> {

let target_commit = repo.find_commit(target.sha)?;
let mut workspace_tree = repo.find_real_tree(&target_commit, Default::default())?;
let mut workspace_tree_id = git2_to_gix_object_id(workspace_tree.id());

if conflicts::is_conflicting(ctx, None)? {
let merge_parent = conflicts::merge_parent(ctx)?.ok_or(anyhow!("No merge parent"))?;
Expand All @@ -49,22 +51,32 @@ pub(crate) fn get_workspace_head(ctx: &CommandContext) -> Result<git2::Oid> {
let merge_base = repo.merge_base(first_branch.head(), merge_parent)?;
workspace_tree = repo.find_commit(merge_base)?.tree()?;
} else {
let gix_repo = ctx.gix_repository_for_merging()?;
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 merge_tree = repo.find_commit(target.sha)?.tree()?;
let branch_tree = repo.find_commit(branch.head())?;
let branch_tree = repo.find_real_tree(&branch_tree, Default::default())?;

let mut index = repo.merge_trees(&merge_tree, &workspace_tree, &branch_tree, None)?;
let branch_head = repo.find_commit(branch.head())?;
let branch_tree_id =
git2_to_gix_object_id(repo.find_real_tree(&branch_head, Default::default())?.id());

let mut merge = gix_repo.merge_trees(
merge_tree_id,
workspace_tree_id,
branch_tree_id,
gix_repo.default_merge_labels(),
merge_options_fail_fast.clone(),
)?;

if !index.has_conflicts() {
workspace_tree = repo.find_tree(index.write_tree_to(repo)?)?;
if !merge.has_unresolved_conflicts(conflict_kind) {
workspace_tree_id = merge.tree.write()?.detach();
} else {
// This branch should have already been unapplied during the "update" command but for some reason that failed
tracing::warn!("Merge conflict between base and {:?}", branch.name);
branch.in_workspace = false;
vb_state.set_branch(branch.clone())?;
}
}
workspace_tree = repo.find_tree(gix_to_git2_oid(workspace_tree_id))?;
}

let committer = gitbutler_repo::signature(SignaturePurpose::Committer)?;
Expand Down
6 changes: 1 addition & 5 deletions crates/gitbutler-branch-actions/src/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use gitbutler_oplog::entry::{OperationKind, SnapshotDetails};
use gitbutler_oplog::{OplogExt, SnapshotExt};
use gitbutler_project::Project;
use gitbutler_reference::normalize_branch_name;
use gitbutler_repo::GixRepositoryExt;
use gitbutler_repo_actions::RepoActionsExt;
use gitbutler_stack::{Branch, CommitOrChangeId, ForgeIdentifier, PatchReferenceUpdate, Series};
use gitbutler_stack::{Stack, StackId, Target};
Expand Down Expand Up @@ -190,10 +189,7 @@ pub fn push_stack(project: &Project, branch_id: StackId, with_force: bool) -> Re

// First fetch, because we dont want to push integrated series
ctx.fetch(&default_target.push_remote_name(), None)?;
let gix_repo = ctx
.gix_repository()?
.for_tree_diffing()?
.with_object_memory();
let gix_repo = ctx.gix_repository_for_merging_non_persisting()?;
let cache = gix_repo.commit_graph_if_enabled()?;
let mut graph = gix_repo.revision_graph(cache.as_ref());
let mut check_commit = IsCommitIntegrated::new(ctx, &default_target, &gix_repo, &mut graph)?;
Expand Down
Loading

0 comments on commit 7fe5d0c

Please sign in to comment.