Skip to content
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

finalize gix-merge integration #5722

Merged
merged 11 commits into from
Dec 13, 2024
388 changes: 199 additions & 189 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 @@ -43,7 +43,7 @@ resolver = "2"
[workspace.dependencies]
bstr = "1.11.0"
# Add the `tracing` or `tracing-detail` features to see more of gitoxide in the logs. Useful to see which programs it invokes.
gix = { version = "0.68.0", default-features = false, features = [] }
gix = { git = "https://github.com/GitoxideLabs/gitoxide", rev = "520c832cfcfb34eb7617be55ebe2719ab35595fd", default-features = false, features = [] }
git2 = { version = "0.19.0", features = [
"vendored-openssl",
"vendored-libgit2",
Expand Down
4 changes: 2 additions & 2 deletions crates/gitbutler-branch-actions/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ 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_oxidize::{git2_to_gix_object_id, gix_to_git2_oid, GixRepositoryExt};
use gitbutler_project::FetchResult;
use gitbutler_reference::{Refname, RemoteRefname};
use gitbutler_repo::{GixRepositoryExt, LogUntil, RepositoryExt};
use gitbutler_repo::{LogUntil, RepositoryExt};
use gitbutler_repo_actions::RepoActionsExt;
use gitbutler_stack::{BranchOwnershipClaims, Stack, Target, VirtualBranchesHandle};
use serde::Serialize;
Expand Down
4 changes: 2 additions & 2 deletions crates/gitbutler-branch-actions/src/branch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ use gitbutler_branch::BranchIdentity;
use gitbutler_branch::ReferenceExtGix;
use gitbutler_command_context::CommandContext;
use gitbutler_diff::DiffByPathMap;
use gitbutler_oxidize::{git2_to_gix_object_id, gix_to_git2_oid};
use gitbutler_oxidize::{git2_to_gix_object_id, gix_to_git2_oid, GixRepositoryExt};
use gitbutler_project::access::WorktreeReadPermission;
use gitbutler_reference::normalize_branch_name;
use gitbutler_reference::RemoteRefname;
use gitbutler_repo::{GixRepositoryExt, RepositoryExt as _};
use gitbutler_repo::RepositoryExt as _;
use gitbutler_serde::BStringForFrontend;
use gitbutler_stack::{Stack as GitButlerBranch, StackId, Target};
use gix::object::tree::diff::Action;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
use super::BranchManager;
use crate::r#virtual as vbranch;
use crate::{
conflicts::RepoConflictsExt, hunk::VirtualBranchHunk, integration::update_workspace_commit,
VirtualBranchesExt,
};
use anyhow::{anyhow, bail, Context, Result};
use gitbutler_branch::BranchCreateRequest;
use gitbutler_branch::{self, dedup};
use gitbutler_cherry_pick::RepositoryExt as _;
use gitbutler_commit::{commit_ext::CommitExt, commit_headers::HasCommitHeaders};
use gitbutler_error::error::Marker;
use gitbutler_oplog::SnapshotExt;
use gitbutler_oxidize::GixRepositoryExt;
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 All @@ -19,12 +24,6 @@ use gitbutler_time::time::now_since_unix_epoch_ms;
use gitbutler_workspace::checkout_branch_trees;
use tracing::instrument;

use super::BranchManager;
use crate::{
conflicts::RepoConflictsExt, hunk::VirtualBranchHunk, integration::update_workspace_commit,
VirtualBranchesExt,
};

impl BranchManager<'_> {
#[instrument(level = tracing::Level::DEBUG, skip(self, perm), err(Debug))]
pub fn create_virtual_branch(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ 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_oxidize::{git2_to_gix_object_id, GixRepositoryExt};
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
Original file line number Diff line number Diff line change
Expand Up @@ -1083,7 +1083,7 @@ mod test {
fn hard_reset_to_externally_amended_commit() {
let test_repository = TestingRepository::open();

let base_commit = dbg!(test_repository.commit_tree(None, &[]));
let base_commit = test_repository.commit_tree(None, &[]);
let local_a = test_repository.commit_tree_with_message(
Some(&base_commit),
"A",
Expand Down Expand Up @@ -1145,7 +1145,7 @@ mod test {
fn hard_reset_to_externally_removed_commit() {
let test_repository = TestingRepository::open();

let base_commit = dbg!(test_repository.commit_tree(None, &[]));
let base_commit = test_repository.commit_tree(None, &[]);
let local_a = test_repository.commit_tree_with_message(
Some(&base_commit),
"A",
Expand Down Expand Up @@ -1212,7 +1212,7 @@ mod test {
fn hard_reset_to_externally_amended_branch() {
let test_repository = TestingRepository::open();

let base_commit = dbg!(test_repository.commit_tree(None, &[]));
let base_commit = test_repository.commit_tree(None, &[]);
let local_a = test_repository.commit_tree_with_message(
Some(&base_commit),
"A",
Expand Down
4 changes: 2 additions & 2 deletions crates/gitbutler-branch-actions/src/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +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_oxidize::{git2_to_gix_object_id, gix_to_git2_oid, GixRepositoryExt};
use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_repo::{GixRepositoryExt, SignaturePurpose};
use gitbutler_repo::SignaturePurpose;
use gitbutler_repo::{LogUntil, RepositoryExt};
use gitbutler_stack::{Stack, VirtualBranchesHandle};
use tracing::instrument;
Expand Down
4 changes: 2 additions & 2 deletions crates/gitbutler-branch-actions/src/upstream_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ use anyhow::{anyhow, bail, Context, 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_oxidize::{git2_to_gix_object_id, gix_to_git2_oid, GixRepositoryExt};
use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_repo::RepositoryExt as _;
use gitbutler_repo::{
rebase::{cherry_rebase_group, gitbutler_merge_commits},
GixRepositoryExt, LogUntil,
LogUntil,
};
use gitbutler_repo_actions::RepoActionsExt as _;
use gitbutler_stack::stack_context::StackContext;
Expand Down
6 changes: 4 additions & 2 deletions crates/gitbutler-branch-actions/src/virtual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ use gitbutler_diff::{trees, GitHunk, Hunk};
use gitbutler_error::error::Code;
use gitbutler_hunk_dependency::RangeCalculationError;
use gitbutler_operating_modes::assure_open_workspace_mode;
use gitbutler_oxidize::{git2_signature_to_gix_signature, git2_to_gix_object_id, gix_to_git2_oid};
use gitbutler_oxidize::{
git2_signature_to_gix_signature, git2_to_gix_object_id, gix_to_git2_oid, GixRepositoryExt,
};
use gitbutler_project::access::WorktreeWritePermission;
use gitbutler_reference::{normalize_branch_name, Refname, RemoteRefname};
use gitbutler_repo::{
rebase::{cherry_rebase, cherry_rebase_group},
GixRepositoryExt, LogUntil, RepositoryExt,
LogUntil, RepositoryExt,
};
use gitbutler_repo_actions::RepoActionsExt;
use gitbutler_stack::{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ fn detect_integrated_commits() {
.into_iter()
.find(|b| b.id == branch1_id)
.unwrap();
repository.merge(&branch.upstream.as_ref().unwrap().name);
repository
.merge(&branch.upstream.as_ref().unwrap().name)
.unwrap();
repository.fetch();
}

Expand Down
11 changes: 1 addition & 10 deletions crates/gitbutler-branch/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,18 @@ version = "0.0.0"
edition = "2021"
authors = ["GitButler <[email protected]>"]
publish = false
autotests = false

[dependencies]
anyhow = "1.0.93"
git2.workspace = true
gix = { workspace = true, features = [] }
gitbutler-reference.workspace = true
gitbutler-serde.workspace = true
gitbutler-id.workspace = true
gitbutler-error.workspace = true
gitbutler-fs.workspace = true
gitbutler-diff.workspace = true
gitbutler-oxidize.workspace = true
gitbutler-time.workspace = true
gitbutler-stack.workspace = true
itertools = "0.13"
toml.workspace = true
serde = { workspace = true, features = ["std"] }
bstr.workspace = true
md5 = "0.7.0"
hex = "0.4.3"
tracing.workspace = true
lazy_static = "1.4.0"

[[test]]
Expand Down
2 changes: 2 additions & 0 deletions crates/gitbutler-cherry-pick/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ publish = false
[dependencies]
gitbutler-commit.workspace = true
git2.workspace = true
gitbutler-oxidize.workspace = true
gix.workspace = true
anyhow.workspace = true
123 changes: 77 additions & 46 deletions crates/gitbutler-cherry-pick/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
// tree_writer.insert(".conflict-side-0", side0.id(), 0o040000)?;
// tree_writer.insert(".conflict-side-1", side1.id(), 0o040000)?;
// tree_writer.insert(".conflict-base-0", base_tree.id(), 0o040000)?;
// tree_writer.insert(".auto-resolution", resolved_tree_id, 0o040000)?;
// tree_writer.insert(".conflict-files", conflicted_files_blob, 0o100644)?;

use std::ops::Deref;

use anyhow::Context;
use git2::MergeOptions;
use anyhow::{Context, Result};
use gitbutler_commit::commit_ext::CommitExt;
use gitbutler_oxidize::git2_to_gix_object_id;

#[derive(Default)]
pub enum ConflictedTreeKey {
Expand Down Expand Up @@ -40,68 +34,105 @@ impl Deref for ConflictedTreeKey {
}

pub trait RepositoryExt {
fn cherry_pick_gitbutler(
&self,
/// Find the real tree of a commit, which is the tree of the commit if it's not in a conflicted state
/// or the tree according to `side` if it is conflicted.
///
/// Unless you want to find a particular side, you likely want to pass Default::default()
/// as the [`side`](ConflictedTreeKey) which will give the automatically resolved resolution
fn find_real_tree(&self, commit: &git2::Commit, side: ConflictedTreeKey) -> Result<git2::Tree>;
}

pub trait GixRepositoryExt {
/// Cherry-pick, but understands GitButler conflicted states.
/// Note that it will automatically resolve conflicts in *our* favor, so any tree produced
/// here can be used.
///
/// This method *should* always be used in favour of native functions.
fn cherry_pick_gitbutler<'repo>(
&'repo self,
head: &git2::Commit,
to_rebase: &git2::Commit,
merge_options: Option<&MergeOptions>,
) -> Result<git2::Index, anyhow::Error>;
fn find_real_tree(
&self,
commit: &git2::Commit,
) -> Result<gix::merge::tree::Outcome<'repo>>;

/// Find the real tree of a commit, which is the tree of the commit if it's not in a conflicted state
/// or the tree according to `side` if it is conflicted.
///
/// Unless you want to find a particular side, you likely want to pass Default::default()
/// as the [`side`](ConflictedTreeKey) which will give the automatically resolved resolution
fn find_real_tree<'repo>(
&'repo self,
commit_id: &gix::oid,
side: ConflictedTreeKey,
) -> Result<git2::Tree, anyhow::Error>;
) -> Result<gix::Id<'repo>>;
}

impl RepositoryExt for git2::Repository {
/// cherry-pick, but understands GitButler conflicted states
///
/// cherry_pick_gitbutler should always be used in favour of libgit2 or gitoxide
/// cherry pick functions
fn cherry_pick_gitbutler(
&self,
fn find_real_tree(&self, commit: &git2::Commit, side: ConflictedTreeKey) -> Result<git2::Tree> {
let tree = commit.tree()?;
if commit.is_conflicted() {
let conflicted_side = tree
.get_name(&side)
.context("Failed to get conflicted side of commit")?;
self.find_tree(conflicted_side.id())
.context("failed to find subtree")
} else {
self.find_tree(tree.id()).context("failed to find subtree")
}
}
}

impl GixRepositoryExt for gix::Repository {
fn cherry_pick_gitbutler<'repo>(
&'repo self,
head: &git2::Commit,
to_rebase: &git2::Commit,
merge_options: Option<&MergeOptions>,
) -> Result<git2::Index, anyhow::Error> {
) -> Result<gix::merge::tree::Outcome<'repo>> {
// we need to do a manual 3-way patch merge
// find the base, which is the parent of to_rebase
let base = if to_rebase.is_conflicted() {
// Use to_rebase's recorded base
self.find_real_tree(to_rebase, ConflictedTreeKey::Base)?
self.find_real_tree(
&git2_to_gix_object_id(to_rebase.id()),
ConflictedTreeKey::Base,
)?
} else {
let base_commit = to_rebase.parent(0)?;
// Use the parent's auto-resolution
self.find_real_tree(&base_commit, Default::default())?
self.find_real_tree(&git2_to_gix_object_id(base_commit.id()), Default::default())?
};
// Get the auto-resolution
let ours = self.find_real_tree(head, Default::default())?;
let ours = self.find_real_tree(&git2_to_gix_object_id(head.id()), Default::default())?;
// Get the original theirs
let thiers = self.find_real_tree(to_rebase, ConflictedTreeKey::Theirs)?;
let theirs = self.find_real_tree(
&git2_to_gix_object_id(to_rebase.id()),
ConflictedTreeKey::Theirs,
)?;

self.merge_trees(&base, &ours, &thiers, merge_options)
.context("failed to merge trees for cherry pick")
use gitbutler_oxidize::GixRepositoryExt;
self.merge_trees(
base,
ours,
theirs,
self.default_merge_labels(),
self.merge_options_force_ours()?,
)
.context("failed to merge trees for cherry pick")
}

/// Find the real tree of a commit, which is the tree of the commit if it's not in a conflicted state
/// or the parent parent tree if it is in a conflicted state
///
/// Unless you want to find a particular side, you likly want to pass Default::default()
/// as the ConfclitedTreeKey which will give the automatically resolved resolution
fn find_real_tree(
&self,
commit: &git2::Commit,
fn find_real_tree<'repo>(
&'repo self,
commit_id: &gix::oid,
side: ConflictedTreeKey,
) -> Result<git2::Tree, anyhow::Error> {
let tree = commit.tree()?;
if commit.is_conflicted() {
) -> Result<gix::Id<'repo>> {
let commit = self.find_commit(commit_id)?;
Ok(if commit.is_conflicted() {
let tree = commit.tree()?;
let conflicted_side = tree
.get_name(&side)
.find_entry(&*side)
.context("Failed to get conflicted side of commit")?;
self.find_tree(conflicted_side.id())
.context("failed to find subtree")
conflicted_side.id()
} else {
self.find_tree(tree.id()).context("failed to find subtree")
}
commit.tree_id()?
})
}
}
1 change: 1 addition & 0 deletions crates/gitbutler-commit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ publish = false

[dependencies]
git2.workspace = true
gix.workspace = true
bstr.workspace = true
uuid.workspace = true
23 changes: 23 additions & 0 deletions crates/gitbutler-commit/src/commit_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,29 @@ impl CommitExt for git2::Commit<'_> {
}
}

impl CommitExt for gix::Commit<'_> {
fn message_bstr(&self) -> &BStr {
self.message_raw()
.expect("valid commit that can be parsed: TODO - allow it to return errors?")
}

fn change_id(&self) -> Option<String> {
self.gitbutler_headers().map(|headers| headers.change_id)
}

fn is_signed(&self) -> bool {
self.decode().map_or(false, |decoded| {
decoded.extra_headers().pgp_signature().is_some()
})
}

fn is_conflicted(&self) -> bool {
self.gitbutler_headers()
.and_then(|headers| headers.conflicted.map(|conflicted| conflicted > 0))
.unwrap_or(false)
}
}

fn contains<'a, I>(iter: I, item: &git2::Commit<'a>) -> bool
where
I: IntoIterator<Item = git2::Commit<'a>>,
Expand Down
Loading
Loading