1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
pub use gix_merge as plumbing;
pub use gix_merge::blob;
///
pub mod virtual_merge_base {
use crate::Id;
/// The outcome produced by [`Repository::virtual_merge_base()`](crate::Repository::virtual_merge_base()).
pub struct Outcome<'repo> {
/// The commit ids of all the virtual merge bases we have produced in the process of recursively merging the merge-bases.
/// As they have been written to the object database, they are still available until they are garbage collected.
/// The last one is the most recently produced and the one returned as `commit_id`.
/// If this list is empty, this means that there was only one merge-base, which itself is already suitable the final merge-base.
pub virtual_merge_bases: Vec<Id<'repo>>,
/// The id of the commit that was created to hold the merged tree.
pub commit_id: Id<'repo>,
/// The hash of the merged tree.
pub tree_id: Id<'repo>,
}
}
///
pub mod commit {
/// The outcome produced by [`Repository::merge_commits()`](crate::Repository::merge_commits()).
#[derive(Clone)]
pub struct Outcome<'a> {
/// The outcome of the actual tree-merge, with the tree editor to write to obtain the actual tree id.
pub tree_merge: crate::merge::tree::Outcome<'a>,
/// The tree id of the base commit we used. This is either…
/// * the single merge-base we found
/// * the first of multiple merge-bases if [Options::with_use_first_merge_base()] was `true`.
/// * the merged tree of all merge-bases, which then isn't linked to an actual commit.
/// * an empty tree, if [Options::with_allow_missing_merge_base()] is enabled.
pub merge_base_tree_id: gix_hash::ObjectId,
/// The object ids of all the commits which were found to be merge-bases, or `None` if there was no merge-base.
pub merge_bases: Option<Vec<gix_hash::ObjectId>>,
/// A list of virtual commits that were created to merge multiple merge-bases into one, the last one being
/// the one we used as merge-base for the merge.
/// As they are not reachable by anything they will be garbage collected, but knowing them provides options.
/// Would be empty if no virtual commit was needed at all as there was only a single merge-base.
/// Otherwise, the last commit id is the one with the `merge_base_tree_id`.
pub virtual_merge_bases: Vec<gix_hash::ObjectId>,
}
/// A way to configure [`Repository::merge_commits()`](crate::Repository::merge_commits()).
#[derive(Default, Debug, Clone)]
pub struct Options {
allow_missing_merge_base: bool,
tree_merge: crate::merge::tree::Options,
use_first_merge_base: bool,
}
impl From<gix_merge::tree::Options> for Options {
fn from(value: gix_merge::tree::Options) -> Self {
Options {
tree_merge: value.into(),
use_first_merge_base: false,
allow_missing_merge_base: false,
}
}
}
impl From<crate::merge::tree::Options> for Options {
fn from(value: crate::merge::tree::Options) -> Self {
Options {
tree_merge: value,
use_first_merge_base: false,
allow_missing_merge_base: false,
}
}
}
impl From<Options> for gix_merge::commit::Options {
fn from(
Options {
allow_missing_merge_base,
tree_merge,
use_first_merge_base,
}: Options,
) -> Self {
gix_merge::commit::Options {
allow_missing_merge_base,
tree_merge: tree_merge.into(),
use_first_merge_base,
}
}
}
/// Builder
impl Options {
/// If `true`, merging unrelated commits is allowed, with the merge-base being assumed as empty tree.
pub fn with_allow_missing_merge_base(mut self, allow_missing_merge_base: bool) -> Self {
self.allow_missing_merge_base = allow_missing_merge_base;
self
}
/// If `true`, do not merge multiple merge-bases into one. Instead, just use the first one.
#[doc(alias = "no_recursive", alias = "git2")]
pub fn with_use_first_merge_base(mut self, use_first_merge_base: bool) -> Self {
self.use_first_merge_base = use_first_merge_base;
self
}
}
}
///
pub mod tree {
use gix_merge::blob::builtin_driver;
pub use gix_merge::tree::{
apply_index_entries, treat_as_unresolved, Conflict, ContentMerge, Resolution, ResolutionFailure,
TreatAsUnresolved,
};
/// The outcome produced by [`Repository::merge_trees()`](crate::Repository::merge_trees()).
#[derive(Clone)]
pub struct Outcome<'repo> {
/// The ready-made (but unwritten) *base* tree, including all non-conflicting changes, and the changes that had
/// conflicts which could be resolved automatically.
///
/// This means, if all of their changes were conflicting, this will be equivalent to the *base* tree.
pub tree: crate::object::tree::Editor<'repo>,
/// The set of conflicts we encountered. Can be empty to indicate there was no conflict.
/// Note that conflicts might have been auto-resolved, but they are listed here for completeness.
/// Use [`has_unresolved_conflicts()`](Outcome::has_unresolved_conflicts()) to see if any action is needed
/// before using [`tree`](Outcome::tree).
pub conflicts: Vec<Conflict>,
/// `true` if `conflicts` contains only a single *unresolved* conflict in the last slot, but possibly more resolved ones.
/// This also makes this outcome a very partial merge that cannot be completed.
pub failed_on_first_unresolved_conflict: bool,
}
impl Outcome<'_> {
/// Return `true` if there is any conflict that would still need to be resolved as they would yield undesirable trees.
/// This is based on `how` to determine what should be considered unresolved.
pub fn has_unresolved_conflicts(&self, how: TreatAsUnresolved) -> bool {
self.conflicts.iter().any(|c| c.is_unresolved(how))
}
/// Returns `true` if `index` changed as we applied conflicting stages to it, using `how` to determine if a
/// conflict should be considered unresolved.
///
/// `removal_mode` decides how unconflicted entries should be removed if they are superseded by
/// their conflicted counterparts.
///
/// It's important that `index` is at the state of [`Self::tree`].
/// Note that in practice, whenever there is a single [conflict](Conflict), this function will return `true`.
pub fn index_changed_after_applying_conflicts(
&self,
index: &mut gix_index::State,
how: TreatAsUnresolved,
removal_mode: apply_index_entries::RemovalMode,
) -> bool {
apply_index_entries(&self.conflicts, how, index, removal_mode)
}
}
/// A way to configure [`Repository::merge_trees()`](crate::Repository::merge_trees()).
#[derive(Default, Debug, Clone)]
pub struct Options {
inner: gix_merge::tree::Options,
file_favor: Option<FileFavor>,
tree_favor: Option<TreeFavor>,
}
impl From<gix_merge::tree::Options> for Options {
fn from(opts: gix_merge::tree::Options) -> Self {
Options {
inner: opts,
file_favor: None,
tree_favor: None,
}
}
}
impl From<Options> for gix_merge::tree::Options {
fn from(value: Options) -> Self {
let mut opts = value.inner;
if let Some(file_favor) = value.file_favor {
let (resolve_binary, resolve_text) = match file_favor {
FileFavor::Ours => (
builtin_driver::binary::ResolveWith::Ours,
builtin_driver::text::Conflict::ResolveWithOurs,
),
FileFavor::Theirs => (
builtin_driver::binary::ResolveWith::Theirs,
builtin_driver::text::Conflict::ResolveWithTheirs,
),
};
opts.symlink_conflicts = Some(resolve_binary);
opts.blob_merge.resolve_binary_with = Some(resolve_binary);
opts.blob_merge.text.conflict = resolve_text;
}
opts.tree_conflicts = value.tree_favor.map(Into::into);
opts
}
}
/// Identify how files should be resolved in case of conflicts.
///
/// This works for…
///
/// * content merges
/// * binary files
/// * symlinks (a form of file after all)
///
/// Note that *union* merges aren't available as they aren't available for binaries or symlinks.
#[derive(Debug, Copy, Clone)]
pub enum FileFavor {
/// Choose *our* side in case of a conflict.
/// Note that this choice is precise, so *ours* hunk will only be chosen if they conflict with *theirs*,
/// so *their* hunks may still show up in the merged result.
Ours,
/// Choose *their* side in case of a conflict.
/// Note that this choice is precise, so *ours* hunk will only be chosen if they conflict with *theirs*,
/// so *their* hunks may still show up in the merged result.
Theirs,
}
/// Control how irreconcilable changes to trees should be resolved.
///
/// Examples for such issues are:
///
/// * *we*: delete, *they*: modify
/// * *we*: rename, *they*: rename to something else
/// * *we*: delete, *they*: rename
///
/// Use this to control which entries are visible to in the resulting tree.
/// Also note that this does not apply to the many tree-related changes are reconcilable.
#[derive(Debug, Copy, Clone)]
pub enum TreeFavor {
/// Choose *our* side in case of a conflict.
/// Note that content-merges are *still* performed according to the [FileFavor].
Ours,
/// Choose the state of the shared common ancestor, dropping both *ours* and *their* changes.
/// Content merges are not performed here.
Ancestor,
}
impl From<TreeFavor> for gix_merge::tree::ResolveWith {
fn from(value: TreeFavor) -> Self {
match value {
TreeFavor::Ours => gix_merge::tree::ResolveWith::Ours,
TreeFavor::Ancestor => gix_merge::tree::ResolveWith::Ancestor,
}
}
}
/// Builder
impl Options {
/// If *not* `None`, rename tracking will be performed when determining the changes of each side of the merge.
pub fn with_rewrites(mut self, rewrites: Option<gix_diff::Rewrites>) -> Self {
self.inner.rewrites = rewrites;
self
}
/// If `Some(what-is-unresolved)`, the first unresolved conflict will cause the entire merge to stop.
/// This is useful to see if there is any conflict, without performing the whole operation, something
/// that can be very relevant during merges that would cause a lot of blob-diffs.
pub fn with_fail_on_conflict(mut self, fail_on_conflict: Option<TreatAsUnresolved>) -> Self {
self.inner.fail_on_conflict = fail_on_conflict;
self
}
/// When `None`, the default, both sides will be treated equally, and in case of conflict an unbiased representation
/// is chosen both for content and for trees, causing a conflict.
///
/// With `Some(favor)` one can choose a side to prefer in order to forcefully resolve an otherwise irreconcilable conflict,
/// loosing information in the process.
pub fn with_file_favor(mut self, file_favor: Option<FileFavor>) -> Self {
self.file_favor = file_favor;
self
}
/// When `None`, the default, both sides will be treated equally, trying to keep both conflicting changes in the tree, possibly
/// by renaming one side to move it out of the way.
///
/// With `Some(favor)` one can choose a side to prefer in order to forcefully resolve an otherwise irreconcilable conflict,
/// loosing information in the process.
pub fn with_tree_favor(mut self, tree_favor: Option<TreeFavor>) -> Self {
self.tree_favor = tree_favor;
self
}
}
}