Skip to content

Commit 5f3f63a

Browse files
committed
feat: add Repository::merge_commits()
It's often more convenient to work with commits when merging, especially when merge-bases are dealt with automatically.
1 parent 9d43b75 commit 5f3f63a

File tree

3 files changed

+175
-3
lines changed

3 files changed

+175
-3
lines changed

gix/src/merge.rs

+84
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,90 @@ pub use gix_merge as plumbing;
22

33
pub use gix_merge::blob;
44

5+
///
6+
pub mod commit {
7+
/// The outcome produced by [`Repository::merge_commits()`](crate::Repository::merge_commits()).
8+
#[derive(Clone)]
9+
pub struct Outcome<'a> {
10+
/// The outcome of the actual tree-merge, with the tree editor to write to obtain the actual tree id.
11+
pub tree_merge: crate::merge::tree::Outcome<'a>,
12+
/// The tree id of the base commit we used. This is either…
13+
/// * the single merge-base we found
14+
/// * the first of multiple merge-bases if [Options::with_use_first_merge_base()] was `true`.
15+
/// * the merged tree of all merge-bases, which then isn't linked to an actual commit.
16+
/// * an empty tree, if [Options::with_allow_missing_merge_base()] is enabled.
17+
pub merge_base_tree_id: gix_hash::ObjectId,
18+
/// The object ids of all the commits which were found to be merge-bases, or `None` if there was no merge-base.
19+
pub merge_bases: Option<Vec<gix_hash::ObjectId>>,
20+
/// A list of virtual commits that were created to merge multiple merge-bases into one, the last one being
21+
/// the one we used as merge-base for the merge.
22+
/// As they are not reachable by anything they will be garbage collected, but knowing them provides options.
23+
/// Would be empty if no virtual commit was needed at all as there was only a single merge-base.
24+
/// Otherwise, the last commit id is the one with the `merge_base_tree_id`.
25+
pub virtual_merge_bases: Vec<gix_hash::ObjectId>,
26+
}
27+
28+
/// A way to configure [`Repository::merge_commits()`](crate::Repository::merge_commits()).
29+
#[derive(Default, Debug, Clone)]
30+
pub struct Options {
31+
allow_missing_merge_base: bool,
32+
tree_merge: crate::merge::tree::Options,
33+
use_first_merge_base: bool,
34+
}
35+
36+
impl From<gix_merge::tree::Options> for Options {
37+
fn from(value: gix_merge::tree::Options) -> Self {
38+
Options {
39+
tree_merge: value.into(),
40+
use_first_merge_base: false,
41+
allow_missing_merge_base: false,
42+
}
43+
}
44+
}
45+
46+
impl From<crate::merge::tree::Options> for Options {
47+
fn from(value: crate::merge::tree::Options) -> Self {
48+
Options {
49+
tree_merge: value,
50+
use_first_merge_base: false,
51+
allow_missing_merge_base: false,
52+
}
53+
}
54+
}
55+
56+
impl From<Options> for gix_merge::commit::Options {
57+
fn from(
58+
Options {
59+
allow_missing_merge_base,
60+
tree_merge,
61+
use_first_merge_base,
62+
}: Options,
63+
) -> Self {
64+
gix_merge::commit::Options {
65+
allow_missing_merge_base,
66+
tree_merge: tree_merge.into(),
67+
use_first_merge_base,
68+
}
69+
}
70+
}
71+
72+
/// Builder
73+
impl Options {
74+
/// If `true`, merging unrelated commits is allowed, with the merge-base being assumed as empty tree.
75+
pub fn with_allow_missing_merge_base(mut self, allow_missing_merge_base: bool) -> Self {
76+
self.allow_missing_merge_base = allow_missing_merge_base;
77+
self
78+
}
79+
80+
/// If `true`, do not merge multiple merge-bases into one. Instead, just use the first one.
81+
#[doc(alias = "no_recursive", alias = "git2")]
82+
pub fn with_use_first_merge_base(mut self, use_first_merge_base: bool) -> Self {
83+
self.use_first_merge_base = use_first_merge_base;
84+
self
85+
}
86+
}
87+
}
88+
589
///
690
pub mod tree {
791
use gix_merge::blob::builtin_driver;

gix/src/repository/merge.rs

+71-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::config::cache::util::ApplyLeniencyDefault;
22
use crate::config::tree;
3-
use crate::repository::{blob_merge_options, merge_resource_cache, merge_trees, tree_merge_options};
3+
use crate::prelude::ObjectIdExt;
4+
use crate::repository::{blob_merge_options, merge_commits, merge_resource_cache, merge_trees, tree_merge_options};
45
use crate::Repository;
56
use gix_merge::blob::builtin_driver::text;
67
use gix_object::Write;
@@ -102,7 +103,7 @@ impl Repository {
102103
}
103104

104105
/// Merge `our_tree` and `their_tree` together, assuming they have the same `ancestor_tree`, to yield a new tree
105-
/// which is provided as [tree editor](gix_object::tree::Editor) to inspect and finalize results at will.
106+
/// which is provided as [tree editor](crate::object::tree::Editor) to inspect and finalize results at will.
106107
/// No change to the worktree or index is made, but objects may be written to the object database as merge results
107108
/// are stored.
108109
/// If these changes should not be observable outside of this instance, consider [enabling object memory](Self::with_object_memory).
@@ -115,7 +116,7 @@ impl Repository {
115116
///
116117
/// ### Performance
117118
///
118-
/// It's highly recommended to [set an object cache](crate::Repository::compute_object_cache_size_for_tree_diffs)
119+
/// It's highly recommended to [set an object cache](Repository::compute_object_cache_size_for_tree_diffs)
119120
/// to avoid extracting the same object multiple times.
120121
pub fn merge_trees(
121122
&self,
@@ -155,4 +156,71 @@ impl Repository {
155156
failed_on_first_unresolved_conflict,
156157
})
157158
}
159+
160+
/// Merge `our_commit` and `their_commit` together to yield a new tree which is provided as [tree editor](crate::object::tree::Editor)
161+
/// to inspect and finalize results at will. The merge-base will be determined automatically between both commits, along with special
162+
/// handling in case there are multiple merge-bases.
163+
/// No change to the worktree or index is made, but objects may be written to the object database as merge results
164+
/// are stored.
165+
/// If these changes should not be observable outside of this instance, consider [enabling object memory](Self::with_object_memory).
166+
///
167+
/// `labels` are typically chosen to identify the refs or names for `our_commit` and `their_commit`, with the ancestor being set
168+
/// automatically as part of the merge-base handling.
169+
///
170+
/// `options` should be initialized with [`Repository::tree_merge_options().into()`](Self::tree_merge_options()).
171+
///
172+
/// ### Performance
173+
///
174+
/// It's highly recommended to [set an object cache](Repository::compute_object_cache_size_for_tree_diffs)
175+
/// to avoid extracting the same object multiple times.
176+
pub fn merge_commits(
177+
&self,
178+
our_commit: impl Into<gix_hash::ObjectId>,
179+
their_commit: impl Into<gix_hash::ObjectId>,
180+
labels: gix_merge::blob::builtin_driver::text::Labels<'_>,
181+
options: crate::merge::commit::Options,
182+
) -> Result<crate::merge::commit::Outcome<'_>, merge_commits::Error> {
183+
let mut diff_cache = self.diff_resource_cache_for_tree_diff()?;
184+
let mut blob_merge = self.merge_resource_cache(Default::default())?;
185+
let commit_graph = self.commit_graph_if_enabled()?;
186+
let mut graph = self.revision_graph(commit_graph.as_ref());
187+
let gix_merge::commit::Outcome {
188+
tree_merge:
189+
gix_merge::tree::Outcome {
190+
tree,
191+
conflicts,
192+
failed_on_first_unresolved_conflict,
193+
},
194+
merge_base_tree_id,
195+
merge_bases,
196+
virtual_merge_bases,
197+
} = gix_merge::commit(
198+
our_commit.into(),
199+
their_commit.into(),
200+
labels,
201+
&mut graph,
202+
&mut diff_cache,
203+
&mut blob_merge,
204+
self,
205+
&mut |id| id.to_owned().attach(self).shorten_or_id().to_string(),
206+
options.into(),
207+
)?;
208+
209+
let validate = self.config.protect_options()?;
210+
let tree_merge = crate::merge::tree::Outcome {
211+
tree: crate::object::tree::Editor {
212+
inner: tree,
213+
validate,
214+
repo: self,
215+
},
216+
conflicts,
217+
failed_on_first_unresolved_conflict,
218+
};
219+
Ok(crate::merge::commit::Outcome {
220+
tree_merge,
221+
merge_base_tree_id,
222+
merge_bases,
223+
virtual_merge_bases,
224+
})
225+
}
158226
}

gix/src/repository/mod.rs

+20
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,26 @@ pub mod merge_trees {
130130
}
131131
}
132132

133+
///
134+
#[cfg(feature = "merge")]
135+
pub mod merge_commits {
136+
/// The error returned by [Repository::merge_commits()](crate::Repository::merge_commits()).
137+
#[derive(Debug, thiserror::Error)]
138+
#[allow(missing_docs)]
139+
pub enum Error {
140+
#[error(transparent)]
141+
OpenCommitGraph(#[from] super::commit_graph_if_enabled::Error),
142+
#[error(transparent)]
143+
MergeResourceCache(#[from] super::merge_resource_cache::Error),
144+
#[error(transparent)]
145+
DiffResourceCache(#[from] super::diff_resource_cache::Error),
146+
#[error(transparent)]
147+
CommitMerge(#[from] gix_merge::commit::Error),
148+
#[error(transparent)]
149+
ValidationOptions(#[from] crate::config::boolean::Error),
150+
}
151+
}
152+
133153
///
134154
#[cfg(feature = "merge")]
135155
pub mod tree_merge_options {

0 commit comments

Comments
 (0)