Skip to content

Replace merge-tree in more places #5416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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::{
CommitOrChangeId, ForgeIdentifier, PatchReference, PatchReferenceUpdate, Series,
Expand Down Expand Up @@ -192,10 +191,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
Loading