Skip to content

Commit 9de2442

Browse files
committed
Replace merge-tree in upstream_integration_statuses
1 parent 7357d94 commit 9de2442

File tree

2 files changed

+66
-22
lines changed

2 files changed

+66
-22
lines changed

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

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
1+
use crate::{
2+
branch_trees::{checkout_branch_trees, compute_updated_branch_head, BranchHeadAndTree},
3+
BranchManagerExt, VirtualBranchesExt as _,
4+
};
15
use anyhow::{anyhow, bail, Result};
26
use gitbutler_cherry_pick::RepositoryExt as _;
37
use gitbutler_command_context::CommandContext;
8+
use gitbutler_oxidize::git2_to_gix_object_id;
49
use gitbutler_project::access::WorktreeWritePermission;
510
use gitbutler_repo::{
611
rebase::{cherry_rebase_group, gitbutler_merge_commits},
7-
LogUntil, RepositoryExt as _,
12+
GixRepositoryExt, LogUntil, RepositoryExt as _,
813
};
914
use gitbutler_repo_actions::RepoActionsExt as _;
1015
use gitbutler_stack::{Stack, StackId, Target, VirtualBranchesHandle};
16+
use gix::prelude::Write;
1117
use serde::{Deserialize, Serialize};
1218

13-
use crate::{
14-
branch_trees::{checkout_branch_trees, compute_updated_branch_head, BranchHeadAndTree},
15-
BranchManagerExt, VirtualBranchesExt as _,
16-
};
17-
1819
#[derive(Serialize, PartialEq, Debug)]
1920
#[serde(tag = "type", content = "subject", rename_all = "camelCase")]
2021
pub enum BranchStatus {
@@ -140,8 +141,19 @@ pub fn upstream_integration_statuses(
140141
..
141142
} = context;
142143
// look up the target and see if there is a new oid
143-
let old_target_tree = repository.find_real_tree(old_target, Default::default())?;
144-
let new_target_tree = repository.find_real_tree(new_target, Default::default())?;
144+
let old_target_tree_id = git2_to_gix_object_id(
145+
repository
146+
.find_real_tree(old_target, Default::default())?
147+
.id(),
148+
);
149+
let new_target_tree_id = git2_to_gix_object_id(
150+
repository
151+
.find_real_tree(new_target, Default::default())?
152+
.id(),
153+
);
154+
let gix_repo = gitbutler_command_context::gix_repository_for_merging(repository.path())?;
155+
let gix_repo_in_memory = gix_repo.clone().with_object_memory();
156+
let (merge_options_fail_fast, conflict_kind) = gix_repo.merge_options_fail_fast()?;
145157

146158
if new_target.id() == old_target.id() {
147159
return Ok(BranchStatuses::UpToDate);
@@ -151,8 +163,10 @@ pub fn upstream_integration_statuses(
151163
.iter()
152164
.map(|virtual_branch| {
153165
let tree = repository.find_tree(virtual_branch.tree)?;
166+
let tree_id = git2_to_gix_object_id(tree.id());
154167
let head = repository.find_commit(virtual_branch.head())?;
155168
let head_tree = repository.find_real_tree(&head, Default::default())?;
169+
let head_tree_id = git2_to_gix_object_id(head_tree.id());
156170

157171
// Try cherry pick the branch's head commit onto the target to
158172
// see if it conflics. This is equivalent to doing a merge
@@ -168,25 +182,33 @@ pub fn upstream_integration_statuses(
168182
};
169183
}
170184

171-
let head_merge_index =
172-
repository.merge_trees(&old_target_tree, &new_target_tree, &head_tree, None)?;
173-
let mut tree_merge_index =
174-
repository.merge_trees(&old_target_tree, &new_target_tree, &tree, None)?;
185+
let mut tree_merge = gix_repo.merge_trees(
186+
old_target_tree_id,
187+
new_target_tree_id,
188+
tree_id,
189+
gix_repo.default_merge_labels(),
190+
merge_options_fail_fast.clone(),
191+
)?;
175192

176193
// Is the branch conflicted?
177194
// A branch can't be integrated if its conflicted
178195
{
179-
let commits_conflicted = head_merge_index.has_conflicts();
196+
let commits_conflicted = gix_repo_in_memory
197+
.merge_trees(
198+
old_target_tree_id,
199+
new_target_tree_id,
200+
head_tree_id,
201+
Default::default(),
202+
merge_options_fail_fast.clone(),
203+
)?
204+
.has_unresolved_conflicts(conflict_kind);
205+
gix_repo_in_memory.objects.reset_object_memory();
180206

181207
// See whether uncommited changes are potentially conflicted
182208
let potentially_conflicted_uncommited_changes = if has_uncommited_changes {
183209
// If the commits are conflicted, we can guarentee that the
184210
// tree will be conflicted.
185-
if commits_conflicted {
186-
true
187-
} else {
188-
tree_merge_index.has_conflicts()
189-
}
211+
commits_conflicted || tree_merge.has_unresolved_conflicts(conflict_kind)
190212
} else {
191213
// If there are no uncommited changes, then there can't be
192214
// any conflicts.
@@ -205,13 +227,20 @@ pub fn upstream_integration_statuses(
205227

206228
// Is the branch fully integrated?
207229
{
230+
if tree_merge.has_unresolved_conflicts(conflict_kind) {
231+
bail!(
232+
"Merge result unexpectedly has conflicts between base, ours, theirs: {old_target_tree_id}, {new_target_tree_id}, {tree_id}"
233+
)
234+
}
208235
// We're safe to write the tree as we've ensured it's
209236
// unconflicted in the previous test.
210-
let tree_merge_index_tree = tree_merge_index.write_tree_to(repository)?;
237+
let tree_merge_index_tree_id = tree_merge
238+
.tree
239+
.write(|tree| gix_repo.write(tree))
240+
.map_err(|err| anyhow!("{err}"))?;
211241

212-
// Identical trees will have the same Oid so we can compare
213-
// the two
214-
if tree_merge_index_tree == new_target_tree.id() {
242+
// Identical trees will have the same Oid so we can compare the two
243+
if tree_merge_index_tree_id == new_target_tree_id {
215244
return Ok((virtual_branch.id, BranchStatus::FullyIntegrated));
216245
}
217246
}

crates/gitbutler-testsupport/src/testing_repository.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,21 @@ impl TestingRepository {
1515
pub fn open() -> Self {
1616
let tempdir = tempdir().unwrap();
1717
let repository = git2::Repository::init_opts(tempdir.path(), &init_opts()).unwrap();
18+
// TODO(ST): remove this once `gix::Repository::index_or_load_from_tree_or_empty()`
19+
// is available and used to get merge/diff resource caches.
20+
// For now we need a resemblance of an initialized repo.
21+
let signature = git2::Signature::now("Caleb", "[email protected]").unwrap();
22+
let empty_tree_id = repository.treebuilder(None).unwrap().write().unwrap();
23+
repository
24+
.commit(
25+
Some("refs/heads/master"),
26+
&signature,
27+
&signature,
28+
"init to prevent load index failure",
29+
&repository.find_tree(empty_tree_id).unwrap(),
30+
&[],
31+
)
32+
.unwrap();
1833

1934
let config = repository.config().unwrap();
2035
match config.open_level(git2::ConfigLevel::Local) {

0 commit comments

Comments
 (0)