Skip to content

Commit

Permalink
Rewrite get_commit_index with gitoxide.
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Dec 7, 2024
1 parent 48d26cc commit 8dfbdc7
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 27 deletions.
4 changes: 3 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion crates/gitbutler-edit-mode/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ publish = false

[dependencies]
git2.workspace = true
gix.workspace = true
anyhow.workspace = true
bstr.workspace = true
gitbutler-branch.workspace = true
Expand All @@ -16,11 +17,12 @@ gitbutler-command-context.workspace = true
gitbutler-operating-modes.workspace = true
gitbutler-project.workspace = true
gitbutler-branch-actions.workspace = true
gitbutler-oxidize.workspace = true
gitbutler-reference.workspace = true
gitbutler-time.workspace = true
gitbutler-oplog.workspace = true
gitbutler-diff.workspace = true
gitbutler-stack.workspace = true
gitbutler-cherry-pick.workspace = true
gitbutler-workspace.workspace = true
serde.workspace = true
tracing.workspace = true
56 changes: 31 additions & 25 deletions crates/gitbutler-edit-mode/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use git2::build::CheckoutBuilder;
use gitbutler_branch_actions::internal::list_virtual_branches;
use gitbutler_branch_actions::{update_workspace_commit, RemoteBranchFile};
use gitbutler_cherry_pick::{ConflictedTreeKey, RepositoryExt as _};
use gitbutler_command_context::CommandContext;
use gitbutler_command_context::{gix_repository_for_merging, CommandContext};
use gitbutler_commit::{
commit_ext::CommitExt,
commit_headers::{CommitHeadersV2, HasCommitHeaders},
Expand All @@ -18,6 +18,7 @@ use gitbutler_operating_modes::{
operating_mode, read_edit_mode_metadata, write_edit_mode_metadata, EditModeMetadata,
OperatingMode, EDIT_BRANCH_REF, WORKSPACE_BRANCH_REF,
};
use gitbutler_oxidize::{git2_to_gix_object_id, gix_to_git2_index, GixRepositoryExt};
use gitbutler_project::access::{WorktreeReadPermission, WorktreeWritePermission};
use gitbutler_reference::{ReferenceName, Refname};
use gitbutler_repo::{rebase::cherry_rebase, RepositoryExt};
Expand All @@ -28,42 +29,47 @@ use serde::Serialize;

pub mod commands;

/// Returns an index of the the tree of `commit` if it is unconflicted, *or* produce a merged tree
/// if `commit` is conflicted. That tree is turned into an index that records the conflicts that occurred
/// during the merge.
fn get_commit_index(repository: &git2::Repository, commit: &git2::Commit) -> Result<git2::Index> {
let commit_tree = commit.tree().context("Failed to get commit's tree")?;
// Checkout the commit as unstaged changes
if commit.is_conflicted() {
let base = commit_tree
.get_name(".conflict-base-0")
.context("Failed to get base")?;
let base = repository
.find_tree(base.id())
.context("Failed to find base tree")?;
// Ours
.context("Failed to get base")?
.id();
let ours = commit_tree
.get_name(".conflict-side-0")
.context("Failed to get base")?;
let ours = repository
.find_tree(ours.id())
.context("Failed to find base tree")?;
// Theirs
.context("Failed to get base")?
.id();
let theirs = commit_tree
.get_name(".conflict-side-1")
.context("Failed to get base")?;
let theirs = repository
.find_tree(theirs.id())
.context("Failed to find base tree")?;

let index = repository
.merge_trees(&base, &ours, &theirs, None)
.context("Failed to merge trees")?;

Ok(index)
.context("Failed to get base")?
.id();

let gix_repo = gix_repository_for_merging(repository.path())?;
// Merge without favoring a side this time to get a tree containing the actual conflicts.
let mut merge_result = gix_repo.merge_trees(
git2_to_gix_object_id(base),
git2_to_gix_object_id(ours),
git2_to_gix_object_id(theirs),
gix_repo.default_merge_labels(),
gix_repo.tree_merge_options()?,
)?;
let merged_tree_id = merge_result.tree.write()?;
let mut index = gix_repo.index_from_tree(&merged_tree_id)?;
if !merge_result.index_changed_after_applying_conflicts(
&mut index,
gix::merge::tree::TreatAsUnresolved::git(),
) {
tracing::warn!("There must be an issue with conflict-commit creation as re-merging the conflicting trees didn't yield a conflicting index.");
}
gix_to_git2_index(&index)
} else {
let mut index = git2::Index::new()?;
index
.read_tree(&commit_tree)
.context("Failed to set index tree")?;

index.read_tree(&commit_tree)?;
Ok(index)
}
}
Expand Down
53 changes: 53 additions & 0 deletions crates/gitbutler-oxidize/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,56 @@ pub fn gix_to_git2_signature(
&time,
)?)
}

/// Convert a `gix` index into a `git2` one.
///
/// Note that this is quite inefficient as it will have to re-allocate all paths.
///
/// ## Note
///
/// * Flags aren't fully supported right now, they are truncated, but good enough to get the stage right.
///
/// ## IMPORTANT
///
/// When removing this in favor of using the native `gix` index, do not forget to prune `REMOVE`d entries
/// or otherwise handle them.
pub fn gix_to_git2_index(index: &gix::index::State) -> anyhow::Result<git2::Index> {
let mut out = git2::Index::new()?;
for entry @ gix::index::Entry {
stat:
gix::index::entry::Stat {
mtime,
ctime,
dev,
ino,
uid,
gid,
size,
},
id,
flags,
mode,
..
} in index.entries()
{
if flags.contains(gix::index::entry::Flags::REMOVE) {
continue;
}
let git2_entry = git2::IndexEntry {
ctime: git2::IndexTime::new(ctime.secs as i32, ctime.nsecs),
mtime: git2::IndexTime::new(mtime.secs as i32, mtime.nsecs),
dev: *dev,
ino: *ino,
mode: mode.bits(),
uid: *uid,
gid: *gid,
file_size: *size,
id: gix_to_git2_oid(*id),
flags: flags.bits() as u16,
flags_extended: 0,
path: entry.path(index).to_owned().into(),
};
out.add(&git2_entry)?
}
Ok(out)
}

0 comments on commit 8dfbdc7

Please sign in to comment.