Skip to content

Commit 6ef051e

Browse files
committed
Replace merge-trees in oplog to use gitoxide.
1 parent e3a701b commit 6ef051e

File tree

4 files changed

+67
-53
lines changed

4 files changed

+67
-53
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/gitbutler-command-context/src/lib.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use anyhow::Result;
22
use gitbutler_project::Project;
3+
use std::path::Path;
34

45
pub struct CommandContext {
56
/// The git repository of the `project` itself.
@@ -90,10 +91,7 @@ impl CommandContext {
9091
/// to correctly figure out author and committer names (i.e. with most global configuration loaded),
9192
/// *and* which will perform diffs quickly thanks to an adequate object cache.
9293
pub fn gix_repository_for_merging(&self) -> Result<gix::Repository> {
93-
let mut repo = gix::open(self.repository().path())?;
94-
let bytes = repo.compute_object_cache_size_for_tree_diffs(&***repo.index_or_empty()?);
95-
repo.object_cache_size_if_unset(bytes);
96-
Ok(repo)
94+
gix_repository_for_merging(self.repository().path())
9795
}
9896

9997
/// Return a newly opened `gitoxide` repository, with all configuration available
@@ -119,5 +117,15 @@ impl CommandContext {
119117
}
120118
}
121119

120+
/// Return a newly opened `gitoxide` repository, with all configuration available
121+
/// to correctly figure out author and committer names (i.e. with most global configuration loaded),
122+
/// *and* which will perform diffs quickly thanks to an adequate object cache.
123+
pub fn gix_repository_for_merging(worktree_or_git_dir: &Path) -> Result<gix::Repository> {
124+
let mut repo = gix::open(worktree_or_git_dir)?;
125+
let bytes = repo.compute_object_cache_size_for_tree_diffs(&***repo.index_or_empty()?);
126+
repo.object_cache_size_if_unset(bytes);
127+
Ok(repo)
128+
}
129+
122130
mod repository_ext;
123131
pub use repository_ext::RepositoryExtLite;

crates/gitbutler-oplog/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ gix = { workspace = true, features = ["dirwalk", "credentials", "parallel"] }
1717
toml.workspace = true
1818
gitbutler-command-context.workspace = true
1919
gitbutler-project.workspace = true
20+
gitbutler-oxidize.workspace = true
2021
gitbutler-branch.workspace = true
2122
gitbutler-serde.workspace = true
2223
gitbutler-fs.workspace = true

crates/gitbutler-oplog/src/oplog.rs

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,26 @@ use std::{
66
time::Duration,
77
};
88

9+
use super::{
10+
entry::{OperationKind, Snapshot, SnapshotDetails, Trailer},
11+
reflog::set_reference_to_oplog,
12+
state::OplogHandle,
13+
};
914
use anyhow::{anyhow, bail, Context, Result};
1015
use git2::{DiffOptions, FileMode};
1116
use gitbutler_command_context::RepositoryExtLite;
1217
use gitbutler_diff::{hunks_by_filepath, FileDiff};
18+
use gitbutler_oxidize::{git2_to_gix_object_id, gix_to_git2_oid};
1319
use gitbutler_project::{
1420
access::{WorktreeReadPermission, WorktreeWritePermission},
1521
Project,
1622
};
17-
use gitbutler_repo::RepositoryExt;
1823
use gitbutler_repo::SignaturePurpose;
24+
use gitbutler_repo::{GixRepositoryExt, RepositoryExt};
1925
use gitbutler_stack::{Stack, VirtualBranchesHandle, VirtualBranchesState};
26+
use gix::prelude::Write;
2027
use tracing::instrument;
2128

22-
use super::{
23-
entry::{OperationKind, Snapshot, SnapshotDetails, Trailer},
24-
reflog::set_reference_to_oplog,
25-
state::OplogHandle,
26-
};
27-
2829
const SNAPSHOT_FILE_LIMIT_BYTES: u64 = 32 * 1024 * 1024;
2930

3031
/// The Oplog allows for crating snapshots of the current state of the project as well as restoring to a previous snapshot.
@@ -165,8 +166,9 @@ impl OplogExt for Project {
165166
limit: usize,
166167
oplog_commit_id: Option<git2::Oid>,
167168
) -> Result<Vec<Snapshot>> {
168-
let repo_path = self.path.as_path();
169-
let repo = git2::Repository::open(repo_path)?;
169+
let worktree_dir = self.path.as_path();
170+
let repo = git2::Repository::open(worktree_dir)?;
171+
let gix_repo = gitbutler_command_context::gix_repository_for_merging(worktree_dir)?;
170172

171173
let traversal_root_id = match oplog_commit_id {
172174
Some(id) => id,
@@ -208,15 +210,16 @@ impl OplogExt for Project {
208210
}
209211

210212
// Get tree id from cache or calculate it
211-
let wd_tree = get_workdir_tree(&mut wd_trees_cache, commit_id, &repo)?;
213+
let wd_tree = get_workdir_tree(&mut wd_trees_cache, commit_id, &repo, &gix_repo)?;
212214

213215
let details = commit
214216
.message()
215217
.and_then(|msg| SnapshotDetails::from_str(msg).ok());
216218

217219
if let Ok(parent) = commit.parent(0) {
218220
// Get tree id from cache or calculate it
219-
let parent_tree = get_workdir_tree(&mut wd_trees_cache, parent.id(), &repo)?;
221+
let parent_tree =
222+
get_workdir_tree(&mut wd_trees_cache, parent.id(), &repo, &gix_repo)?;
220223

221224
let mut opts = DiffOptions::new();
222225
opts.include_untracked(true);
@@ -279,13 +282,14 @@ impl OplogExt for Project {
279282

280283
fn snapshot_diff(&self, sha: git2::Oid) -> Result<HashMap<PathBuf, FileDiff>> {
281284
let worktree_dir = self.path.as_path();
285+
let gix_repo = gitbutler_command_context::gix_repository_for_merging(worktree_dir)?;
282286
let repo = git2::Repository::init(worktree_dir)?;
283287

284288
let commit = repo.find_commit(sha)?;
285289

286-
let wd_tree_id = tree_from_applied_vbranches(&repo, commit.id())?;
290+
let wd_tree_id = tree_from_applied_vbranches(&gix_repo, commit.id())?;
287291
let wd_tree = repo.find_tree(wd_tree_id)?;
288-
let old_wd_tree_id = tree_from_applied_vbranches(&repo, commit.parent(0)?.id())?;
292+
let old_wd_tree_id = tree_from_applied_vbranches(&gix_repo, commit.parent(0)?.id())?;
289293
let old_wd_tree = repo.find_tree(old_wd_tree_id)?;
290294

291295
repo.ignore_large_files_in_diffs(SNAPSHOT_FILE_LIMIT_BYTES)?;
@@ -317,9 +321,10 @@ fn get_workdir_tree<'a>(
317321
wd_trees_cache: &mut HashMap<git2::Oid, git2::Oid>,
318322
commit_id: git2::Oid,
319323
repo: &'a git2::Repository,
324+
gix_repo: &gix::Repository,
320325
) -> Result<git2::Tree<'a>, anyhow::Error> {
321326
if let Entry::Vacant(e) = wd_trees_cache.entry(commit_id) {
322-
if let Ok(wd_tree_id) = tree_from_applied_vbranches(repo, commit_id) {
327+
if let Ok(wd_tree_id) = tree_from_applied_vbranches(gix_repo, commit_id) {
323328
e.insert(wd_tree_id);
324329
}
325330
}
@@ -574,7 +579,8 @@ fn restore_snapshot(
574579
"We will not change a worktree which for some reason isn't on the workspace branch",
575580
)?;
576581

577-
let workdir_tree_id = tree_from_applied_vbranches(&repo, snapshot_commit_id)?;
582+
let gix_repo = gitbutler_command_context::gix_repository_for_merging(worktree_dir)?;
583+
let workdir_tree_id = tree_from_applied_vbranches(&gix_repo, snapshot_commit_id)?;
578584
let workdir_tree = repo.find_tree(workdir_tree_id)?;
579585

580586
repo.ignore_large_files_in_diffs(SNAPSHOT_FILE_LIMIT_BYTES)?;
@@ -794,59 +800,57 @@ fn deserialize_commit(
794800
}
795801

796802
/// Creates a tree that is the merge of all applied branches from a given snapshot and returns the tree id.
803+
/// Note that `repo` must have caching setup for merges.
797804
fn tree_from_applied_vbranches(
798-
repo: &git2::Repository,
805+
repo: &gix::Repository,
799806
snapshot_commit_id: git2::Oid,
800807
) -> Result<git2::Oid> {
801-
let snapshot_commit = repo.find_commit(snapshot_commit_id)?;
808+
let snapshot_commit = repo.find_commit(git2_to_gix_object_id(snapshot_commit_id))?;
802809
let snapshot_tree = snapshot_commit.tree()?;
803810

804811
let target_tree_entry = snapshot_tree
805-
.get_name("target_tree")
806-
.context("failed to get target tree entry")?;
807-
let target_tree = repo
808-
.find_tree(target_tree_entry.id())
809-
.context("failed to convert target tree entry to tree")?;
812+
.lookup_entry_by_path("target_tree")?
813+
.context("no entry at 'target_entry'")?;
814+
let target_tree_id = target_tree_entry.id().detach();
810815

811816
let vb_toml_entry = snapshot_tree
812-
.get_name("virtual_branches.toml")
817+
.lookup_entry_by_path("virtual_branches.toml")?
813818
.context("failed to get virtual_branches.toml blob")?;
814-
// virtual_branches.toml blob
815819
let vb_toml_blob = repo
816820
.find_blob(vb_toml_entry.id())
817821
.context("failed to convert virtual_branches tree entry to blob")?;
818822

819-
let vbs_from_toml: VirtualBranchesState = toml::from_str(from_utf8(vb_toml_blob.content())?)?;
820-
let applied_branch_trees: Vec<git2::Oid> = vbs_from_toml
823+
let vbs_from_toml: VirtualBranchesState = toml::from_str(from_utf8(&vb_toml_blob.data)?)?;
824+
let applied_branch_trees: Vec<_> = vbs_from_toml
821825
.list_branches_in_workspace()?
822826
.iter()
823-
.map(|b| b.tree)
827+
.map(|b| git2_to_gix_object_id(b.tree))
824828
.collect();
825829

826-
let mut workdir_tree_id = target_tree.id();
827-
let base_tree = target_tree;
828-
let mut current_ours = base_tree.clone();
829-
830-
for branch in applied_branch_trees {
831-
let branch_tree = repo.find_tree(branch)?;
832-
let mut merge_options: git2::MergeOptions = git2::MergeOptions::new();
833-
merge_options.fail_on_conflict(false);
834-
let mut workdir_temp_index = repo.merge_trees(
835-
&base_tree,
836-
&current_ours,
837-
&branch_tree,
838-
Some(&merge_options),
830+
let mut workdir_tree_id = target_tree_id;
831+
let base_tree_id = target_tree_id;
832+
let mut current_ours_id = target_tree_id;
833+
834+
let (merge_option_fail_fast, conflict_kind) = repo.merge_options_fail_fast()?;
835+
for branch_id in applied_branch_trees {
836+
let mut merge = repo.merge_trees(
837+
base_tree_id,
838+
current_ours_id,
839+
branch_id,
840+
repo.default_merge_labels(),
841+
merge_option_fail_fast.clone(),
839842
)?;
840-
match workdir_temp_index.write_tree_to(repo) {
841-
Ok(id) => {
842-
workdir_tree_id = id;
843-
current_ours = repo.find_tree(workdir_tree_id)?;
844-
}
845-
Err(_err) => {
846-
tracing::warn!("Failed to merge tree {branch} - this branch is probably applied at a time when it should not be");
847-
}
843+
if merge.has_unresolved_conflicts(conflict_kind) {
844+
tracing::warn!("Failed to merge tree {branch_id} - this branch is probably applied at a time when it should not be");
845+
} else {
846+
let id = merge
847+
.tree
848+
.write(|tree| repo.write(tree))
849+
.map_err(|err| anyhow!("{err}"))?;
850+
workdir_tree_id = id;
851+
current_ours_id = id;
848852
}
849853
}
850854

851-
Ok(workdir_tree_id)
855+
Ok(gix_to_git2_oid(workdir_tree_id))
852856
}

0 commit comments

Comments
 (0)