Skip to content

Commit 94d77fd

Browse files
committed
feat: add Repository::merge_trees()
1 parent 241a21f commit 94d77fd

File tree

5 files changed

+110
-12
lines changed

5 files changed

+110
-12
lines changed

gix/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ revparse-regex = ["regex", "revision"]
139139
blob-diff = ["gix-diff/blob", "attributes"]
140140

141141
## Add functions to specifically merge files, using the standard three-way merge that git offers.
142-
blob-merge = ["dep:gix-merge", "attributes"]
142+
blob-merge = ["blob-diff", "dep:gix-merge", "attributes"]
143143

144144
## Make it possible to turn a tree into a stream of bytes, which can be decoded to entries and turned into various other formats.
145145
worktree-stream = ["gix-worktree-stream", "attributes"]

gix/src/config/tree/sections/merge.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::config;
22
use crate::config::tree::SubSectionRequirement;
33
use crate::config::{
4-
tree::{diff, keys, Key, Merge, Section},
4+
tree::{keys, Key, Merge, Section},
55
Tree,
66
};
77

@@ -15,7 +15,8 @@ impl Merge {
1515
"The limit is actually squared, so 1000 stands for up to 1 million diffs if fuzzy rename tracking is enabled",
1616
);
1717
/// The `merge.renames` key.
18-
pub const RENAMES: diff::Renames = diff::Renames::new_renames("renames", &config::Tree::MERGE);
18+
#[cfg(feature = "blob-merge")]
19+
pub const RENAMES: super::diff::Renames = super::diff::Renames::new_renames("renames", &config::Tree::MERGE);
1920
/// The `merge.renormalize` key
2021
pub const RENORMALIZE: keys::Boolean = keys::Boolean::new_boolean("renormalize", &Tree::MERGE);
2122
/// The `merge.default` key
@@ -43,6 +44,7 @@ impl Section for Merge {
4344
fn keys(&self) -> &[&dyn Key] {
4445
&[
4546
&Self::RENAME_LIMIT,
47+
#[cfg(feature = "blob-merge")]
4648
&Self::RENAMES,
4749
&Self::RENORMALIZE,
4850
&Self::DEFAULT,

gix/src/diff.rs

+14-6
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ pub mod rename {
122122

123123
///
124124
#[cfg(feature = "blob-diff")]
125-
mod utils {
125+
pub(crate) mod utils {
126126
use gix_diff::{rewrites::Copies, Rewrites};
127127

128128
use crate::{
@@ -172,10 +172,18 @@ mod utils {
172172
config: &gix_config::File<'static>,
173173
lenient: bool,
174174
) -> Result<Option<Rewrites>, new_rewrites::Error> {
175-
let key = "diff.renames";
175+
new_rewrites_inner(config, lenient, &Diff::RENAMES, &Diff::RENAME_LIMIT)
176+
}
177+
178+
pub fn new_rewrites_inner(
179+
config: &gix_config::File<'static>,
180+
lenient: bool,
181+
renames: &'static crate::config::tree::diff::Renames,
182+
rename_limit: &'static crate::config::tree::keys::UnsignedInteger,
183+
) -> Result<Option<Rewrites>, new_rewrites::Error> {
176184
let copies = match config
177-
.boolean(key)
178-
.map(|value| Diff::RENAMES.try_into_renames(value))
185+
.boolean(renames)
186+
.map(|value| renames.try_into_renames(value))
179187
.transpose()
180188
.with_leniency(lenient)?
181189
{
@@ -191,8 +199,8 @@ mod utils {
191199
Ok(Rewrites {
192200
copies,
193201
limit: config
194-
.integer("diff.renameLimit")
195-
.map(|value| Diff::RENAME_LIMIT.try_into_usize(value))
202+
.integer(rename_limit)
203+
.map(|value| rename_limit.try_into_usize(value))
196204
.transpose()
197205
.with_leniency(lenient)?
198206
.unwrap_or(default.limit),

gix/src/repository/merge.rs

+59-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
use crate::config::cache::util::ApplyLeniencyDefault;
22
use crate::config::tree;
3-
use crate::repository::{blob_merge_options, merge_resource_cache};
3+
use crate::repository::{blob_merge_options, merge_resource_cache, merge_trees, tree_merge_options};
44
use crate::Repository;
55
use gix_merge::blob::builtin_driver::text;
6+
use gix_object::Write;
67
use std::borrow::Cow;
78

89
/// Merge-utilities
910
impl Repository {
1011
/// Create a resource cache that can hold the three resources needed for a three-way merge. `worktree_roots`
1112
/// determines which side of the merge is read from the worktree, or from which worktree.
1213
///
13-
/// The platform can be used to setup resources and finally perform a merge.
14+
/// The platform can be used to set up resources and finally perform a merge among blobs.
1415
///
1516
/// Note that the current index is used for attribute queries.
1617
pub fn merge_resource_cache(
@@ -55,7 +56,8 @@ impl Repository {
5556
Ok(gix_merge::blob::Platform::new(filter, mode, attrs, drivers, options))
5657
}
5758

58-
/// Return options for use with [`gix_merge::blob::PlatformRef::merge()`].
59+
/// Return options for use with [`gix_merge::blob::PlatformRef::merge()`], accessible through
60+
/// [merge_resource_cache()](Self::merge_resource_cache).
5961
pub fn blob_merge_options(&self) -> Result<gix_merge::blob::platform::merge::Options, blob_merge_options::Error> {
6062
Ok(gix_merge::blob::platform::merge::Options {
6163
is_virtual_ancestor: false,
@@ -79,4 +81,58 @@ impl Repository {
7981
},
8082
})
8183
}
84+
85+
/// Read all relevant configuration options to instantiate options for use in [`merge_trees()`](Self::merge_trees).
86+
pub fn tree_merge_options(&self) -> Result<gix_merge::tree::Options, tree_merge_options::Error> {
87+
Ok(gix_merge::tree::Options {
88+
rewrites: crate::diff::utils::new_rewrites_inner(
89+
&self.config.resolved,
90+
self.config.lenient_config,
91+
&tree::Merge::RENAMES,
92+
&tree::Merge::RENAME_LIMIT,
93+
)?,
94+
blob_merge: self.blob_merge_options()?,
95+
blob_merge_command_ctx: self.command_context()?,
96+
fail_on_conflict: None,
97+
marker_size_multiplier: 0,
98+
symlink_conflicts: None,
99+
allow_lossy_resolution: false,
100+
})
101+
}
102+
103+
/// Merge `our_tree` and `their_tree` together, assuming they have the same `ancestor_tree`, to yield a new tree
104+
/// which is provided as [tree editor](gix_object::tree::Editor) to inspect and finalize results at will.
105+
/// No change to the worktree or index is made, but objects may be written to the object database as merge results
106+
/// are stored.
107+
/// If these changes should not be observable outside of this instance, consider [enabling object memory](Self::with_object_memory).
108+
///
109+
/// Note that `ancestor_tree` can be the [empty tree hash](gix_hash::ObjectId::empty_tree) to indicate no common ancestry.
110+
///
111+
/// `labels` are typically chosen to identify the refs or names for `our_tree` and `their_tree` and `ancestor_tree` respectively.
112+
///
113+
/// `options` should be initialized with [`tree_merge_options()`](Self::tree_merge_options()).
114+
// TODO: Use `crate::merge::Options` here and add niceties such as setting the resolution strategy.
115+
pub fn merge_trees(
116+
&self,
117+
ancestor_tree: impl AsRef<gix_hash::oid>,
118+
our_tree: impl AsRef<gix_hash::oid>,
119+
their_tree: impl AsRef<gix_hash::oid>,
120+
labels: gix_merge::blob::builtin_driver::text::Labels<'_>,
121+
options: gix_merge::tree::Options,
122+
) -> Result<gix_merge::tree::Outcome<'_>, merge_trees::Error> {
123+
let mut diff_cache = self.diff_resource_cache_for_tree_diff()?;
124+
let mut blob_merge = self.merge_resource_cache(Default::default())?;
125+
Ok(gix_merge::tree(
126+
ancestor_tree.as_ref(),
127+
our_tree.as_ref(),
128+
their_tree.as_ref(),
129+
labels,
130+
&self.objects,
131+
|buf| self.objects.write_buf(gix_object::Kind::Blob, buf),
132+
&mut Default::default(),
133+
&mut diff_cache,
134+
&mut blob_merge,
135+
options,
136+
)?)
137+
}
82138
}

gix/src/repository/mod.rs

+32
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,38 @@ pub mod merge_resource_cache {
112112
}
113113
}
114114

115+
///
116+
#[cfg(feature = "blob-merge")]
117+
pub mod merge_trees {
118+
/// The error returned by [Repository::merge_trees()](crate::Repository::merge_trees()).
119+
#[derive(Debug, thiserror::Error)]
120+
#[allow(missing_docs)]
121+
pub enum Error {
122+
#[error(transparent)]
123+
MergeResourceCache(#[from] super::merge_resource_cache::Error),
124+
#[error(transparent)]
125+
DiffResourceCache(#[from] super::diff_resource_cache::Error),
126+
#[error(transparent)]
127+
TreeMerge(#[from] gix_merge::tree::Error),
128+
}
129+
}
130+
131+
///
132+
#[cfg(feature = "blob-merge")]
133+
pub mod tree_merge_options {
134+
/// The error returned by [Repository::tree_merge_options()](crate::Repository::tree_merge_options()).
135+
#[derive(Debug, thiserror::Error)]
136+
#[allow(missing_docs)]
137+
pub enum Error {
138+
#[error(transparent)]
139+
BlobMergeOptions(#[from] super::blob_merge_options::Error),
140+
#[error(transparent)]
141+
RewritesConfig(#[from] crate::diff::new_rewrites::Error),
142+
#[error(transparent)]
143+
CommandContext(#[from] crate::config::command_context::Error),
144+
}
145+
}
146+
115147
///
116148
#[cfg(feature = "blob-diff")]
117149
pub mod diff_resource_cache {

0 commit comments

Comments
 (0)