Skip to content

Commit 7fe5d0c

Browse files
authored
Merge pull request #5416 from gitbutlerapp/tree-merge
Replace `merge-tree` in more places
2 parents 94109d0 + 1031de1 commit 7fe5d0c

File tree

23 files changed

+704
-435
lines changed

23 files changed

+704
-435
lines changed

Cargo.lock

Lines changed: 52 additions & 51 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ resolver = "2"
4242
[workspace.dependencies]
4343
bstr = "1.10.0"
4444
# Add the `tracing` or `tracing-detail` features to see more of gitoxide in the logs. Useful to see which programs it invokes.
45-
gix = { git = "https://github.com/Byron/gitoxide", rev = "3fb989be21c739bbfeac93953c1685e7c6cd2106", default-features = false, features = [
45+
gix = { git = "https://github.com/Byron/gitoxide", rev = "a8765330fc16997dee275866b18a128dec1c5d55", default-features = false, features = [
4646
] }
4747
git2 = { version = "0.19.0", features = [
4848
"vendored-openssl",

crates/gitbutler-branch-actions/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ publish = false
99
tracing.workspace = true
1010
anyhow = "1.0.92"
1111
git2.workspace = true
12-
gix = { workspace = true, features = ["blob-diff", "revision", "blob-merge"] }
12+
gix = { workspace = true, features = ["blob-diff", "revision", "merge"] }
1313
tokio.workspace = true
1414
gitbutler-oplog.workspace = true
1515
gitbutler-repo.workspace = true

crates/gitbutler-branch-actions/src/base.rs

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
use std::{path::Path, time};
22

3-
use anyhow::{anyhow, Context, Result};
3+
use crate::{
4+
conflicts::RepoConflictsExt,
5+
hunk::VirtualBranchHunk,
6+
integration::update_workspace_commit,
7+
remote::{commit_to_remote_commit, RemoteCommit},
8+
VirtualBranchesExt,
9+
};
10+
use anyhow::{anyhow, bail, Context, Result};
411
use gitbutler_branch::GITBUTLER_WORKSPACE_REFERENCE;
512
use gitbutler_command_context::CommandContext;
613
use gitbutler_error::error::Marker;
14+
use gitbutler_oxidize::{git2_to_gix_object_id, gix_to_git2_oid};
715
use gitbutler_project::FetchResult;
816
use gitbutler_reference::{Refname, RemoteRefname};
9-
use gitbutler_repo::{LogUntil, RepositoryExt};
17+
use gitbutler_repo::{GixRepositoryExt, LogUntil, RepositoryExt};
1018
use gitbutler_repo_actions::RepoActionsExt;
1119
use gitbutler_stack::{BranchOwnershipClaims, Stack, Target, VirtualBranchesHandle};
1220
use serde::Serialize;
1321

14-
use crate::{
15-
conflicts::RepoConflictsExt,
16-
hunk::VirtualBranchHunk,
17-
integration::update_workspace_commit,
18-
remote::{commit_to_remote_commit, RemoteCommit},
19-
VirtualBranchesExt,
20-
};
21-
2222
#[derive(Debug, Serialize, PartialEq, Clone)]
2323
#[serde(rename_all = "camelCase")]
2424
pub struct BaseBranch {
@@ -50,8 +50,8 @@ pub(crate) fn get_base_branch_data(ctx: &CommandContext) -> Result<BaseBranch> {
5050
}
5151

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

70-
let target_commit = ctx
71-
.repository()
70+
let target_commit = repo
7271
.find_commit(default_target.sha)
7372
.context("failed to find target commit")?;
7473

75-
let base_tree = target_commit
76-
.tree()
77-
.context("failed to get base tree from commit")?;
78-
let mut final_tree = target_commit
79-
.tree()
80-
.context("failed to get base tree from commit")?;
74+
let base_tree = git2_to_gix_object_id(target_commit.tree_id());
75+
let mut final_tree_id = git2_to_gix_object_id(target_commit.tree_id());
76+
let gix_repo = ctx.gix_repository_for_merging()?;
77+
let (merge_options_fail_fast, conflict_kind) = gix_repo.merge_options_fail_fast()?;
8178
for branch in &virtual_branches {
8279
// merge this branches tree with our tree
83-
let branch_head = ctx
84-
.repository()
85-
.find_commit(branch.head())
86-
.context("failed to find branch head")?;
87-
let branch_tree = branch_head
88-
.tree()
89-
.context("failed to get branch head tree")?;
90-
let mut result = ctx
91-
.repository()
92-
.merge_trees(&base_tree, &final_tree, &branch_tree, None)
93-
.context("failed to merge")?;
94-
let final_tree_oid = result
95-
.write_tree_to(ctx.repository())
96-
.context("failed to write tree")?;
97-
final_tree = ctx
98-
.repository()
99-
.find_tree(final_tree_oid)
100-
.context("failed to find written tree")?;
80+
let branch_tree_id = git2_to_gix_object_id(
81+
repo.find_commit(branch.head())
82+
.context("failed to find branch head")?
83+
.tree_id(),
84+
);
85+
let mut merge = gix_repo.merge_trees(
86+
base_tree,
87+
final_tree_id,
88+
branch_tree_id,
89+
gix_repo.default_merge_labels(),
90+
merge_options_fail_fast.clone(),
91+
)?;
92+
if merge.has_unresolved_conflicts(conflict_kind) {
93+
bail!("Merge failed with conflicts");
94+
}
95+
final_tree_id = merge.tree.write()?.detach();
10196
}
10297

103-
ctx.repository()
104-
.checkout_tree_builder(&final_tree)
98+
let final_tree = repo.find_tree(gix_to_git2_oid(final_tree_id))?;
99+
repo.checkout_tree_builder(&final_tree)
105100
.force()
106101
.checkout()
107102
.context("failed to checkout tree")?;

crates/gitbutler-branch-actions/src/branch_manager/branch_creation.rs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use gitbutler_error::error::Marker;
88
use gitbutler_oplog::SnapshotExt;
99
use gitbutler_project::access::WorktreeWritePermission;
1010
use gitbutler_reference::{Refname, RemoteRefname};
11+
use gitbutler_repo::GixRepositoryExt;
1112
use gitbutler_repo::{
1213
rebase::{cherry_rebase_group, gitbutler_merge_commits},
1314
LogUntil, RepositoryExt,
@@ -301,29 +302,27 @@ impl BranchManager<'_> {
301302
))?;
302303

303304
// Branch is out of date, merge or rebase it
304-
let merge_base_tree = repo
305+
let merge_base_tree_id = repo
305306
.find_commit(merge_base)
306307
.context(format!("failed to find merge base commit {}", merge_base))?
307308
.tree()
308-
.context("failed to find merge base tree")?;
309-
310-
let branch_tree = repo
311-
.find_tree(branch.tree)
312-
.context("failed to find branch tree")?;
309+
.context("failed to find merge base tree")?
310+
.id();
311+
let branch_tree_id = branch.tree;
313312

314313
// We don't support having two branches applied that conflict with each other
315314
{
316-
let uncommited_changes_tree = repo.create_wd_tree()?;
317-
let branch_merged_with_other_applied_branches = repo
318-
.merge_trees(
319-
&merge_base_tree,
320-
&branch_tree,
321-
&uncommited_changes_tree,
322-
None,
315+
let uncommited_changes_tree_id = repo.create_wd_tree()?.id();
316+
let gix_repo = self.ctx.gix_repository_for_merging_non_persisting()?;
317+
let merges_cleanly = gix_repo
318+
.merges_cleanly_compat(
319+
merge_base_tree_id,
320+
branch_tree_id,
321+
uncommited_changes_tree_id,
323322
)
324323
.context("failed to merge trees")?;
325324

326-
if branch_merged_with_other_applied_branches.has_conflicts() {
325+
if !merges_cleanly {
327326
for branch in vb_state
328327
.list_branches_in_workspace()?
329328
.iter()

crates/gitbutler-branch-actions/src/branch_manager/branch_removal.rs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ use git2::Commit;
55
use gitbutler_branch::BranchExt;
66
use gitbutler_commit::commit_headers::CommitHeadersV2;
77
use gitbutler_oplog::SnapshotExt;
8+
use gitbutler_oxidize::git2_to_gix_object_id;
9+
use gitbutler_oxidize::gix_to_git2_oid;
810
use gitbutler_project::access::WorktreeWritePermission;
911
use gitbutler_reference::{normalize_branch_name, ReferenceName, Refname};
12+
use gitbutler_repo::GixRepositoryExt;
1013
use gitbutler_repo::RepositoryExt;
1114
use gitbutler_repo::SignaturePurpose;
1215
use gitbutler_repo_actions::RepoActionsExt;
@@ -79,7 +82,10 @@ impl BranchManager<'_> {
7982

8083
let repo = self.ctx.repository();
8184

82-
let base_tree = target_commit.tree().context("failed to get target tree")?;
85+
let base_tree_id = target_commit
86+
.tree()
87+
.context("failed to get target tree")?
88+
.id();
8389

8490
let applied_statuses = get_applied_status(self.ctx, None)
8591
.context("failed to get status by branch")?
@@ -98,13 +104,14 @@ impl BranchManager<'_> {
98104
num_branches = applied_statuses.len() - 1
99105
)
100106
.entered();
101-
applied_statuses
107+
let gix_repo = self.ctx.gix_repository()?;
108+
let merge_options = gix_repo.tree_merge_options()?;
109+
let final_tree_id = applied_statuses
102110
.into_iter()
103111
.filter(|(branch, _)| branch.id != branch_id)
104-
.fold(
105-
target_commit.tree().context("failed to get target tree"),
106-
|final_tree, status| {
107-
let final_tree = final_tree?;
112+
.try_fold(
113+
git2_to_gix_object_id(target_commit.tree_id()),
114+
|final_tree_id, status| -> Result<_> {
108115
let branch = status.0;
109116
let files = status
110117
.1
@@ -113,14 +120,18 @@ impl BranchManager<'_> {
113120
.collect::<Vec<(PathBuf, Vec<VirtualBranchHunk>)>>();
114121
let tree_oid =
115122
gitbutler_diff::write::hunks_onto_oid(self.ctx, branch.head(), files)?;
116-
let branch_tree = repo.find_tree(tree_oid)?;
117-
let mut result =
118-
repo.merge_trees(&base_tree, &final_tree, &branch_tree, None)?;
119-
let final_tree_oid = result.write_tree_to(repo)?;
120-
repo.find_tree(final_tree_oid)
121-
.context("failed to find tree")
123+
let mut merge = gix_repo.merge_trees(
124+
git2_to_gix_object_id(base_tree_id),
125+
final_tree_id,
126+
git2_to_gix_object_id(tree_oid),
127+
gix_repo.default_merge_labels(),
128+
merge_options.clone(),
129+
)?;
130+
let final_tree_id = merge.tree.write()?.detach();
131+
Ok(final_tree_id)
122132
},
123-
)?
133+
)?;
134+
repo.find_tree(gix_to_git2_oid(final_tree_id))?
124135
};
125136

126137
let _span = tracing::debug_span!("checkout final tree").entered();

crates/gitbutler-branch-actions/src/branch_trees.rs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1+
use crate::VirtualBranchesExt as _;
12
use anyhow::{bail, Result};
23
use gitbutler_cherry_pick::RepositoryExt;
34
use gitbutler_command_context::CommandContext;
45
use gitbutler_commit::commit_ext::CommitExt as _;
6+
use gitbutler_oxidize::{git2_to_gix_object_id, gix_to_git2_oid};
57
use gitbutler_project::access::WorktreeWritePermission;
68
use gitbutler_repo::rebase::cherry_rebase_group;
7-
use gitbutler_repo::RepositoryExt as _;
9+
use gitbutler_repo::{GixRepositoryExt, RepositoryExt as _};
810
use gitbutler_stack::Stack;
9-
10-
use crate::VirtualBranchesExt as _;
11+
use tracing::instrument;
1112

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

41-
let merge_base_tree = repository.find_commit(merge_base)?.tree()?;
42-
43-
let mut final_tree = merge_base_tree.clone();
43+
let gix_repo = ctx.gix_repository_for_merging()?;
44+
let merge_base_tree_id =
45+
git2_to_gix_object_id(repository.find_commit(merge_base)?.tree_id());
46+
let mut final_tree_id = merge_base_tree_id;
4447

48+
let (merge_options_fail_fast, conflict_kind) = gix_repo.merge_options_fail_fast()?;
4549
for branch in branches {
46-
let theirs = repository.find_tree(branch.tree)?;
47-
let mut merge_index =
48-
repository.merge_trees(&merge_base_tree, &final_tree, &theirs, None)?;
49-
50-
if merge_index.has_conflicts() {
50+
let their_tree_id = git2_to_gix_object_id(branch.tree);
51+
let mut merge = gix_repo.merge_trees(
52+
merge_base_tree_id,
53+
final_tree_id,
54+
their_tree_id,
55+
gix_repo.default_merge_labels(),
56+
merge_options_fail_fast.clone(),
57+
)?;
58+
59+
if merge.has_unresolved_conflicts(conflict_kind) {
5160
bail!("There appears to be conflicts between the virtual branches");
5261
};
5362

54-
let tree_oid = merge_index.write_tree_to(repository)?;
55-
final_tree = repository.find_tree(tree_oid)?;
63+
final_tree_id = merge.tree.write()?.detach();
5664
}
5765

66+
let final_tree = repository.find_tree(gix_to_git2_oid(final_tree_id))?;
5867
repository
5968
.checkout_tree_builder(&final_tree)
6069
.force()

crates/gitbutler-branch-actions/src/integration.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ use gitbutler_command_context::CommandContext;
99
use gitbutler_commit::commit_ext::CommitExt;
1010
use gitbutler_error::error::Marker;
1111
use gitbutler_operating_modes::OPEN_WORKSPACE_REFS;
12+
use gitbutler_oxidize::{git2_to_gix_object_id, gix_to_git2_oid};
1213
use gitbutler_project::access::WorktreeWritePermission;
13-
use gitbutler_repo::SignaturePurpose;
14+
use gitbutler_repo::{GixRepositoryExt, SignaturePurpose};
1415
use gitbutler_repo::{LogUntil, RepositoryExt};
1516
use gitbutler_stack::{Stack, VirtualBranchesHandle};
1617
use tracing::instrument;
@@ -41,6 +42,7 @@ pub(crate) fn get_workspace_head(ctx: &CommandContext) -> Result<git2::Oid> {
4142

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

4547
if conflicts::is_conflicting(ctx, None)? {
4648
let merge_parent = conflicts::merge_parent(ctx)?.ok_or(anyhow!("No merge parent"))?;
@@ -49,22 +51,32 @@ pub(crate) fn get_workspace_head(ctx: &CommandContext) -> Result<git2::Oid> {
4951
let merge_base = repo.merge_base(first_branch.head(), merge_parent)?;
5052
workspace_tree = repo.find_commit(merge_base)?.tree()?;
5153
} else {
54+
let gix_repo = ctx.gix_repository_for_merging()?;
55+
let (merge_options_fail_fast, conflict_kind) = gix_repo.merge_options_fail_fast()?;
56+
let merge_tree_id = git2_to_gix_object_id(repo.find_commit(target.sha)?.tree_id());
5257
for branch in virtual_branches.iter_mut() {
53-
let merge_tree = repo.find_commit(target.sha)?.tree()?;
54-
let branch_tree = repo.find_commit(branch.head())?;
55-
let branch_tree = repo.find_real_tree(&branch_tree, Default::default())?;
56-
57-
let mut index = repo.merge_trees(&merge_tree, &workspace_tree, &branch_tree, None)?;
58+
let branch_head = repo.find_commit(branch.head())?;
59+
let branch_tree_id =
60+
git2_to_gix_object_id(repo.find_real_tree(&branch_head, Default::default())?.id());
61+
62+
let mut merge = gix_repo.merge_trees(
63+
merge_tree_id,
64+
workspace_tree_id,
65+
branch_tree_id,
66+
gix_repo.default_merge_labels(),
67+
merge_options_fail_fast.clone(),
68+
)?;
5869

59-
if !index.has_conflicts() {
60-
workspace_tree = repo.find_tree(index.write_tree_to(repo)?)?;
70+
if !merge.has_unresolved_conflicts(conflict_kind) {
71+
workspace_tree_id = merge.tree.write()?.detach();
6172
} else {
6273
// This branch should have already been unapplied during the "update" command but for some reason that failed
6374
tracing::warn!("Merge conflict between base and {:?}", branch.name);
6475
branch.in_workspace = false;
6576
vb_state.set_branch(branch.clone())?;
6677
}
6778
}
79+
workspace_tree = repo.find_tree(gix_to_git2_oid(workspace_tree_id))?;
6880
}
6981

7082
let committer = gitbutler_repo::signature(SignaturePurpose::Committer)?;

crates/gitbutler-branch-actions/src/stack.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use gitbutler_oplog::entry::{OperationKind, SnapshotDetails};
77
use gitbutler_oplog::{OplogExt, SnapshotExt};
88
use gitbutler_project::Project;
99
use gitbutler_reference::normalize_branch_name;
10-
use gitbutler_repo::GixRepositoryExt;
1110
use gitbutler_repo_actions::RepoActionsExt;
1211
use gitbutler_stack::{Branch, CommitOrChangeId, ForgeIdentifier, PatchReferenceUpdate, Series};
1312
use gitbutler_stack::{Stack, StackId, Target};
@@ -190,10 +189,7 @@ pub fn push_stack(project: &Project, branch_id: StackId, with_force: bool) -> Re
190189

191190
// First fetch, because we dont want to push integrated series
192191
ctx.fetch(&default_target.push_remote_name(), None)?;
193-
let gix_repo = ctx
194-
.gix_repository()?
195-
.for_tree_diffing()?
196-
.with_object_memory();
192+
let gix_repo = ctx.gix_repository_for_merging_non_persisting()?;
197193
let cache = gix_repo.commit_graph_if_enabled()?;
198194
let mut graph = gix_repo.revision_graph(cache.as_ref());
199195
let mut check_commit = IsCommitIntegrated::new(ctx, &default_target, &gix_repo, &mut graph)?;

0 commit comments

Comments
 (0)