Skip to content

Commit 89cdb07

Browse files
committed
Rewrite get_commit_index with gitoxide.
1 parent a0f601b commit 89cdb07

File tree

4 files changed

+86
-27
lines changed

4 files changed

+86
-27
lines changed

Cargo.lock

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/gitbutler-edit-mode/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ publish = false
77

88
[dependencies]
99
git2.workspace = true
10+
gix.workspace = true
1011
anyhow.workspace = true
1112
bstr.workspace = true
1213
gitbutler-branch.workspace = true
@@ -16,11 +17,12 @@ gitbutler-command-context.workspace = true
1617
gitbutler-operating-modes.workspace = true
1718
gitbutler-project.workspace = true
1819
gitbutler-branch-actions.workspace = true
20+
gitbutler-oxidize.workspace = true
1921
gitbutler-reference.workspace = true
20-
gitbutler-time.workspace = true
2122
gitbutler-oplog.workspace = true
2223
gitbutler-diff.workspace = true
2324
gitbutler-stack.workspace = true
2425
gitbutler-cherry-pick.workspace = true
2526
gitbutler-workspace.workspace = true
2627
serde.workspace = true
28+
tracing.workspace = true

crates/gitbutler-edit-mode/src/lib.rs

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use git2::build::CheckoutBuilder;
88
use gitbutler_branch_actions::internal::list_virtual_branches;
99
use gitbutler_branch_actions::{update_workspace_commit, RemoteBranchFile};
1010
use gitbutler_cherry_pick::{ConflictedTreeKey, RepositoryExt as _};
11-
use gitbutler_command_context::CommandContext;
11+
use gitbutler_command_context::{gix_repository_for_merging, CommandContext};
1212
use gitbutler_commit::{
1313
commit_ext::CommitExt,
1414
commit_headers::{CommitHeadersV2, HasCommitHeaders},
@@ -18,6 +18,7 @@ use gitbutler_operating_modes::{
1818
operating_mode, read_edit_mode_metadata, write_edit_mode_metadata, EditModeMetadata,
1919
OperatingMode, EDIT_BRANCH_REF, WORKSPACE_BRANCH_REF,
2020
};
21+
use gitbutler_oxidize::{git2_to_gix_object_id, gix_to_git2_index, GixRepositoryExt};
2122
use gitbutler_project::access::{WorktreeReadPermission, WorktreeWritePermission};
2223
use gitbutler_reference::{ReferenceName, Refname};
2324
use gitbutler_repo::{rebase::cherry_rebase, RepositoryExt};
@@ -28,42 +29,48 @@ use serde::Serialize;
2829

2930
pub mod commands;
3031

32+
/// Returns an index of the the tree of `commit` if it is unconflicted, *or* produce a merged tree
33+
/// if `commit` is conflicted. That tree is turned into an index that records the conflicts that occurred
34+
/// during the merge.
3135
fn get_commit_index(repository: &git2::Repository, commit: &git2::Commit) -> Result<git2::Index> {
3236
let commit_tree = commit.tree().context("Failed to get commit's tree")?;
3337
// Checkout the commit as unstaged changes
3438
if commit.is_conflicted() {
3539
let base = commit_tree
3640
.get_name(".conflict-base-0")
37-
.context("Failed to get base")?;
38-
let base = repository
39-
.find_tree(base.id())
40-
.context("Failed to find base tree")?;
41-
// Ours
41+
.context("Failed to get base")?
42+
.id();
4243
let ours = commit_tree
4344
.get_name(".conflict-side-0")
44-
.context("Failed to get base")?;
45-
let ours = repository
46-
.find_tree(ours.id())
47-
.context("Failed to find base tree")?;
48-
// Theirs
45+
.context("Failed to get base")?
46+
.id();
4947
let theirs = commit_tree
5048
.get_name(".conflict-side-1")
51-
.context("Failed to get base")?;
52-
let theirs = repository
53-
.find_tree(theirs.id())
54-
.context("Failed to find base tree")?;
55-
56-
let index = repository
57-
.merge_trees(&base, &ours, &theirs, None)
58-
.context("Failed to merge trees")?;
59-
60-
Ok(index)
49+
.context("Failed to get base")?
50+
.id();
51+
52+
let gix_repo = gix_repository_for_merging(repository.path())?;
53+
// Merge without favoring a side this time to get a tree containing the actual conflicts.
54+
let mut merge_result = gix_repo.merge_trees(
55+
git2_to_gix_object_id(base),
56+
git2_to_gix_object_id(ours),
57+
git2_to_gix_object_id(theirs),
58+
gix_repo.default_merge_labels(),
59+
gix_repo.tree_merge_options()?,
60+
)?;
61+
let merged_tree_id = merge_result.tree.write()?;
62+
let mut index = gix_repo.index_from_tree(&merged_tree_id)?;
63+
if !merge_result.index_changed_after_applying_conflicts(
64+
&mut index,
65+
gix::merge::tree::TreatAsUnresolved::git(),
66+
gix::merge::tree::apply_index_entries::RemovalMode::Mark,
67+
) {
68+
tracing::warn!("There must be an issue with conflict-commit creation as re-merging the conflicting trees didn't yield a conflicting index.");
69+
}
70+
gix_to_git2_index(&index)
6171
} else {
6272
let mut index = git2::Index::new()?;
63-
index
64-
.read_tree(&commit_tree)
65-
.context("Failed to set index tree")?;
66-
73+
index.read_tree(&commit_tree)?;
6774
Ok(index)
6875
}
6976
}

crates/gitbutler-oxidize/src/lib.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,51 @@ pub fn gix_to_git2_signature(
5353
&time,
5454
)?)
5555
}
56+
57+
/// Convert a `gix` index into a `git2` one, while skipping over entries that are marked for removal.
58+
///
59+
/// Note that this is quite inefficient as it will have to re-allocate all paths.
60+
///
61+
/// ## Note
62+
///
63+
/// * Flags aren't fully supported right now, they are truncated, but good enough to get the *stage* right.
64+
pub fn gix_to_git2_index(index: &gix::index::State) -> anyhow::Result<git2::Index> {
65+
let mut out = git2::Index::new()?;
66+
for entry @ gix::index::Entry {
67+
stat:
68+
gix::index::entry::Stat {
69+
mtime,
70+
ctime,
71+
dev,
72+
ino,
73+
uid,
74+
gid,
75+
size,
76+
},
77+
id,
78+
flags,
79+
mode,
80+
..
81+
} in index.entries()
82+
{
83+
if flags.contains(gix::index::entry::Flags::REMOVE) {
84+
continue;
85+
}
86+
let git2_entry = git2::IndexEntry {
87+
ctime: git2::IndexTime::new(ctime.secs as i32, ctime.nsecs),
88+
mtime: git2::IndexTime::new(mtime.secs as i32, mtime.nsecs),
89+
dev: *dev,
90+
ino: *ino,
91+
mode: mode.bits(),
92+
uid: *uid,
93+
gid: *gid,
94+
file_size: *size,
95+
id: gix_to_git2_oid(*id),
96+
flags: flags.bits() as u16,
97+
flags_extended: 0,
98+
path: entry.path(index).to_owned().into(),
99+
};
100+
out.add(&git2_entry)?
101+
}
102+
Ok(out)
103+
}

0 commit comments

Comments
 (0)