Skip to content

Commit 182f9ab

Browse files
committed
add platform tests and implementation
That way, the platform can be used to perform actual merges. This will also be a good chance to try the API.
1 parent a6f3e30 commit 182f9ab

File tree

17 files changed

+1480
-1172
lines changed

17 files changed

+1480
-1172
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gix-merge/Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ doctest = false
1717
[features]
1818
default = ["blob"]
1919
## Enable diffing of blobs using imara-diff, which also allows for a generic rewrite tracking implementation.
20-
blob = ["dep:imara-diff", "dep:gix-filter", "dep:gix-worktree", "dep:gix-path", "dep:gix-fs", "dep:gix-command", "dep:gix-tempfile", "dep:gix-trace"]
20+
blob = ["dep:imara-diff", "dep:gix-filter", "dep:gix-worktree", "dep:gix-path", "dep:gix-fs", "dep:gix-command", "dep:gix-tempfile", "dep:gix-trace", "dep:gix-quote"]
2121
## Data structures implement `serde::Serialize` and `serde::Deserialize`.
2222
serde = ["dep:serde", "gix-hash/serde", "gix-object/serde"]
2323

@@ -31,6 +31,7 @@ gix-path = { version = "^0.10.11", path = "../gix-path", optional = true }
3131
gix-fs = { version = "^0.11.3", path = "../gix-fs", optional = true }
3232
gix-tempfile = { version = "^14.0.0", path = "../gix-tempfile", optional = true }
3333
gix-trace = { version = "^0.1.10", path = "../gix-trace", optional = true }
34+
gix-quote = { version = "^0.4.12", path = "../gix-quote", optional = true }
3435

3536
thiserror = "1.0.63"
3637
imara-diff = { version = "0.1.7", optional = true }

gix-merge/src/blob/builtin_driver/text/function.rs

+69-78
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ use crate::blob::builtin_driver::text::utils::{
33
hunks_differ_in_diff3, take_intersecting, tokens, write_ancestor, write_conflict_marker, write_hunks,
44
zealously_contract_hunks, CollectHunks, Hunk, Side,
55
};
6-
use crate::blob::builtin_driver::text::{ConflictStyle, Options, ResolveWith};
6+
use crate::blob::builtin_driver::text::{Conflict, ConflictStyle, Labels, Options};
77
use crate::blob::Resolution;
8-
use bstr::BStr;
98

109
/// Merge `current` and `other` with `ancestor` as base according to `opts`.
1110
///
12-
/// Use `current_label`, `other_label` and `ancestor_label` to annotate conflict sections.
11+
/// Use `labels` to annotate conflict sections.
1312
///
1413
/// `input` is for reusing memory for lists of tokens, but note that it grows indefinitely
1514
/// while tokens for `current`, `ancestor` and `other` are added.
@@ -23,12 +22,14 @@ use bstr::BStr;
2322
pub fn merge<'a>(
2423
out: &mut Vec<u8>,
2524
input: &mut imara_diff::intern::InternedInput<&'a [u8]>,
25+
Labels {
26+
ancestor: ancestor_label,
27+
current: current_label,
28+
other: other_label,
29+
}: Labels<'_>,
2630
current: &'a [u8],
27-
current_label: Option<&BStr>,
2831
ancestor: &'a [u8],
29-
ancestor_label: Option<&BStr>,
3032
other: &'a [u8],
31-
other_label: Option<&BStr>,
3233
opts: Options,
3334
) -> Resolution {
3435
out.clear();
@@ -77,9 +78,9 @@ pub fn merge<'a>(
7778
.expect("at least one entry"),
7879
&mut filled_hunks,
7980
);
80-
match opts.on_conflict {
81-
None => {
82-
let (hunks_front_and_back, num_hunks_front) = match opts.conflict_style {
81+
match opts.conflict {
82+
Conflict::Keep { style, marker_size } => {
83+
let (hunks_front_and_back, num_hunks_front) = match style {
8384
ConflictStyle::Merge | ConflictStyle::ZealousDiff3 => {
8485
zealously_contract_hunks(&mut filled_hunks, &mut intersecting, input, &current_tokens)
8586
}
@@ -130,28 +131,22 @@ pub fn merge<'a>(
130131
)
131132
.or_else(|| detect_line_ending(our_hunks, input, &current_tokens))
132133
.unwrap_or(b"\n".into());
133-
match opts.conflict_style {
134+
match style {
134135
ConflictStyle::Merge => {
135136
if contains_lines(our_hunks) || contains_lines(their_hunks) {
136137
resolution = Resolution::Conflict;
137-
write_conflict_marker(out, b'<', current_label, opts.marker_size, nl);
138+
write_conflict_marker(out, b'<', current_label, marker_size, nl);
138139
write_hunks(our_hunks, input, &current_tokens, out);
139-
write_conflict_marker(out, b'=', None, opts.marker_size, nl);
140+
write_conflict_marker(out, b'=', None, marker_size, nl);
140141
write_hunks(their_hunks, input, &current_tokens, out);
141-
write_conflict_marker(out, b'>', other_label, opts.marker_size, nl);
142+
write_conflict_marker(out, b'>', other_label, marker_size, nl);
142143
}
143144
}
144145
ConflictStyle::Diff3 | ConflictStyle::ZealousDiff3 => {
145146
if contains_lines(our_hunks) || contains_lines(their_hunks) {
146-
if hunks_differ_in_diff3(
147-
opts.conflict_style,
148-
our_hunks,
149-
their_hunks,
150-
input,
151-
&current_tokens,
152-
) {
147+
if hunks_differ_in_diff3(style, our_hunks, their_hunks, input, &current_tokens) {
153148
resolution = Resolution::Conflict;
154-
write_conflict_marker(out, b'<', current_label, opts.marker_size, nl);
149+
write_conflict_marker(out, b'<', current_label, marker_size, nl);
155150
write_hunks(our_hunks, input, &current_tokens, out);
156151
let ancestor_hunk = Hunk {
157152
before: first_hunk.before.start..last_hunk.before.end,
@@ -161,11 +156,11 @@ pub fn merge<'a>(
161156
let ancestor_hunk = std::slice::from_ref(&ancestor_hunk);
162157
let ancestor_nl =
163158
detect_line_ending_or_nl(ancestor_hunk, input, &current_tokens);
164-
write_conflict_marker(out, b'|', ancestor_label, opts.marker_size, ancestor_nl);
159+
write_conflict_marker(out, b'|', ancestor_label, marker_size, ancestor_nl);
165160
write_hunks(ancestor_hunk, input, &current_tokens, out);
166-
write_conflict_marker(out, b'=', None, opts.marker_size, nl);
161+
write_conflict_marker(out, b'=', None, marker_size, nl);
167162
write_hunks(their_hunks, input, &current_tokens, out);
168-
write_conflict_marker(out, b'>', other_label, opts.marker_size, nl);
163+
write_conflict_marker(out, b'>', other_label, marker_size, nl);
169164
} else {
170165
write_hunks(our_hunks, input, &current_tokens, out);
171166
}
@@ -176,64 +171,60 @@ pub fn merge<'a>(
176171
write_hunks(back_hunks, input, &current_tokens, out);
177172
ancestor_integrated_until = last_hunk.before.end;
178173
}
179-
Some(resolve) => {
180-
match resolve {
181-
ResolveWith::Ours | ResolveWith::Theirs => {
182-
let (our_hunks, their_hunks) = match filled_hunks_side {
183-
Side::Current => (&filled_hunks, &intersecting),
184-
Side::Other => (&intersecting, &filled_hunks),
185-
Side::Ancestor => {
186-
unreachable!("initial hunks are never ancestors")
187-
}
188-
};
189-
let hunks_to_write = if resolve == ResolveWith::Ours {
190-
our_hunks
191-
} else {
192-
their_hunks
193-
};
194-
if let Some(first_hunk) = hunks_to_write.first() {
195-
write_ancestor(input, ancestor_integrated_until, first_hunk.before.start as usize, out);
196-
}
197-
write_hunks(hunks_to_write, input, &current_tokens, out);
198-
if let Some(last_hunk) = hunks_to_write.last() {
199-
ancestor_integrated_until = last_hunk.before.end;
200-
}
174+
Conflict::ResolveWithOurs | Conflict::ResolveWithTheirs => {
175+
let (our_hunks, their_hunks) = match filled_hunks_side {
176+
Side::Current => (&filled_hunks, &intersecting),
177+
Side::Other => (&intersecting, &filled_hunks),
178+
Side::Ancestor => {
179+
unreachable!("initial hunks are never ancestors")
201180
}
202-
ResolveWith::Union => {
203-
let (hunks_front_and_back, num_hunks_front) =
204-
zealously_contract_hunks(&mut filled_hunks, &mut intersecting, input, &current_tokens);
181+
};
182+
let hunks_to_write = if opts.conflict == Conflict::ResolveWithOurs {
183+
our_hunks
184+
} else {
185+
their_hunks
186+
};
187+
if let Some(first_hunk) = hunks_to_write.first() {
188+
write_ancestor(input, ancestor_integrated_until, first_hunk.before.start as usize, out);
189+
}
190+
write_hunks(hunks_to_write, input, &current_tokens, out);
191+
if let Some(last_hunk) = hunks_to_write.last() {
192+
ancestor_integrated_until = last_hunk.before.end;
193+
}
194+
}
195+
Conflict::ResolveWithUnion => {
196+
let (hunks_front_and_back, num_hunks_front) =
197+
zealously_contract_hunks(&mut filled_hunks, &mut intersecting, input, &current_tokens);
205198

206-
let (our_hunks, their_hunks) = match filled_hunks_side {
207-
Side::Current => (&filled_hunks, &intersecting),
208-
Side::Other => (&intersecting, &filled_hunks),
209-
Side::Ancestor => {
210-
unreachable!("initial hunks are never ancestors")
211-
}
212-
};
213-
let (front_hunks, back_hunks) = hunks_front_and_back.split_at(num_hunks_front);
214-
let first_hunk = front_hunks
215-
.first()
216-
.or(our_hunks.first())
217-
.expect("at least one hunk to write");
218-
write_ancestor(input, ancestor_integrated_until, first_hunk.before.start as usize, out);
219-
write_hunks(front_hunks, input, &current_tokens, out);
220-
assure_ends_with_nl(out, detect_line_ending_or_nl(front_hunks, input, &current_tokens));
221-
write_hunks(our_hunks, input, &current_tokens, out);
222-
assure_ends_with_nl(out, detect_line_ending_or_nl(our_hunks, input, &current_tokens));
223-
write_hunks(their_hunks, input, &current_tokens, out);
224-
if !back_hunks.is_empty() {
225-
assure_ends_with_nl(out, detect_line_ending_or_nl(their_hunks, input, &current_tokens));
226-
}
227-
write_hunks(back_hunks, input, &current_tokens, out);
228-
let last_hunk = back_hunks
229-
.last()
230-
.or(their_hunks.last())
231-
.or(our_hunks.last())
232-
.or(front_hunks.last())
233-
.expect("at least one hunk");
234-
ancestor_integrated_until = last_hunk.before.end;
199+
let (our_hunks, their_hunks) = match filled_hunks_side {
200+
Side::Current => (&filled_hunks, &intersecting),
201+
Side::Other => (&intersecting, &filled_hunks),
202+
Side::Ancestor => {
203+
unreachable!("initial hunks are never ancestors")
235204
}
236205
};
206+
let (front_hunks, back_hunks) = hunks_front_and_back.split_at(num_hunks_front);
207+
let first_hunk = front_hunks
208+
.first()
209+
.or(our_hunks.first())
210+
.expect("at least one hunk to write");
211+
write_ancestor(input, ancestor_integrated_until, first_hunk.before.start as usize, out);
212+
write_hunks(front_hunks, input, &current_tokens, out);
213+
assure_ends_with_nl(out, detect_line_ending_or_nl(front_hunks, input, &current_tokens));
214+
write_hunks(our_hunks, input, &current_tokens, out);
215+
assure_ends_with_nl(out, detect_line_ending_or_nl(our_hunks, input, &current_tokens));
216+
write_hunks(their_hunks, input, &current_tokens, out);
217+
if !back_hunks.is_empty() {
218+
assure_ends_with_nl(out, detect_line_ending_or_nl(their_hunks, input, &current_tokens));
219+
}
220+
write_hunks(back_hunks, input, &current_tokens, out);
221+
let last_hunk = back_hunks
222+
.last()
223+
.or(their_hunks.last())
224+
.or(our_hunks.last())
225+
.or(front_hunks.last())
226+
.expect("at least one hunk");
227+
ancestor_integrated_until = last_hunk.before.end;
237228
}
238229
}
239230
} else {

gix-merge/src/blob/builtin_driver/text/mod.rs

+48-13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use bstr::BStr;
2+
13
/// The way the built-in [text driver](crate::blob::BuiltinDriver::Text) will express
24
/// merge conflicts in the resulting file.
35
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
@@ -48,41 +50,74 @@ pub enum ConflictStyle {
4850
ZealousDiff3,
4951
}
5052

53+
/// The set of labels to annotate conflict markers with.
54+
///
55+
/// That way it becomes clearer where the content of conflicts are originating from.
56+
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
57+
pub struct Labels<'a> {
58+
pub ancestor: Option<&'a BStr>,
59+
pub current: Option<&'a BStr>,
60+
pub other: Option<&'a BStr>,
61+
}
62+
5163
/// Options for the builtin [text driver](crate::blob::BuiltinDriver::Text).
5264
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
5365
pub struct Options {
5466
/// Determine of the diff will be performed.
5567
/// Defaults to [`imara_diff::Algorithm::Myers`].
5668
pub diff_algorithm: imara_diff::Algorithm,
57-
/// How to visualize conflicts in merged files.
58-
pub conflict_style: ConflictStyle,
59-
/// The amount of markers to draw, defaults to 7, i.e. `<<<<<<<`
60-
pub marker_size: usize,
61-
/// Decide what to do to automatically resolve conflicts.
69+
/// Decide what to do to automatically resolve conflicts, or to keep them
6270
/// If `None`, add conflict markers according to `conflict_style` and `marker_size`.
63-
pub on_conflict: Option<ResolveWith>,
71+
pub conflict: Conflict,
6472
}
6573

6674
impl Default for Options {
6775
fn default() -> Self {
6876
Options {
69-
conflict_style: Default::default(),
70-
marker_size: 7,
71-
on_conflict: None,
77+
conflict: Default::default(),
7278
diff_algorithm: imara_diff::Algorithm::Myers,
7379
}
7480
}
7581
}
7682

7783
/// What to do to resolve a conflict.
7884
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
79-
pub enum ResolveWith {
85+
pub enum Conflict {
86+
/// Keep the conflict by marking it in the source file.
87+
Keep {
88+
/// How to visualize conflicts in merged files.
89+
style: ConflictStyle,
90+
/// The amount of markers to draw, defaults to 7, i.e. `<<<<<<<`
91+
marker_size: usize,
92+
},
8093
/// Chose our side to resolve a conflict.
81-
Ours,
94+
ResolveWithOurs,
8295
/// Chose their side to resolve a conflict.
83-
Theirs,
96+
ResolveWithTheirs,
8497
/// Place our and their lines one after another, in any order
85-
Union,
98+
ResolveWithUnion,
99+
}
100+
101+
impl Conflict {
102+
/// The amount of conflict marker characters to print by default.
103+
pub const DEFAULT_MARKER_SIZE: usize = 7;
104+
105+
/// The amount of conflict markers to print if this instance contains them, or `None` otherwise
106+
pub fn marker_size(&self) -> Option<usize> {
107+
match self {
108+
Conflict::Keep { marker_size, .. } => Some(*marker_size),
109+
Conflict::ResolveWithOurs | Conflict::ResolveWithTheirs | Conflict::ResolveWithUnion => None,
110+
}
111+
}
112+
}
113+
114+
impl Default for Conflict {
115+
fn default() -> Self {
116+
Conflict::Keep {
117+
style: Default::default(),
118+
marker_size: Conflict::DEFAULT_MARKER_SIZE,
119+
}
120+
}
86121
}
87122

88123
pub(super) mod function;

gix-merge/src/blob/builtin_driver/text/utils.rs

-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,6 @@ fn ancestor_hunk(start: u32, num_lines: u32) -> Hunk {
168168
///
169169
/// Return a new vector of all the hunks that were removed from front and back, with partial hunks inserted,
170170
/// along with the amount of hunks that go front, with the remaining going towards the back.
171-
// TODO: refactor so hunks and their associated data can go into an array for easier handling.
172171
#[must_use]
173172
pub fn zealously_contract_hunks(
174173
a_hunks: &mut Vec<Hunk>,

gix-merge/src/blob/mod.rs

+25-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// TODO: remove this - only needed while &mut Vec<u8> isn't used.
22
#![allow(clippy::ptr_arg)]
33

4+
use crate::blob::platform::{DriverChoice, ResourceRef};
45
use bstr::BString;
56
use std::path::PathBuf;
67

@@ -83,7 +84,7 @@ pub struct Driver {
8384
/// * **%L**
8485
/// - The conflict-marker size as positive number.
8586
/// * **%P**
86-
/// - The path in which the merged result will be stored.
87+
/// - The path in which the merged result would be stored, as workspace-relative path, of the current/ours side.
8788
/// * **%S**
8889
/// - The conflict-label for the common ancestor or *base*.
8990
/// * **%X**
@@ -98,6 +99,8 @@ pub struct Driver {
9899
/// ```
99100
/// <driver-program> .merge_file_nR2Qs1 .merge_file_WYXCJe .merge_file_UWbzrm 7 file e2a2970 HEAD feature
100101
/// ```
102+
///
103+
/// The driver is expected to leave its version in the file at `%A`, by overwriting it.
101104
pub command: BString,
102105
/// If `true`, this is the `name` of the driver to use when a virtual-merge-base is created, as a merge of all
103106
/// available merge-bases if there are more than one.
@@ -157,3 +160,24 @@ pub struct Platform {
157160
/// The way we convert resources into mergeable states.
158161
filter_mode: pipeline::Mode,
159162
}
163+
164+
/// The product of a [`prepare_merge()`](Platform::prepare_merge()) call to finally
165+
/// perform the merge and retrieve the merge results.
166+
#[derive(Copy, Clone)]
167+
pub struct PlatformRef<'parent> {
168+
/// The platform that hosts the resources, used to access drivers.
169+
pub(super) parent: &'parent Platform,
170+
/// The current or our side of the merge operation.
171+
pub current: ResourceRef<'parent>,
172+
/// The ancestor or base of the merge operation.
173+
pub ancestor: ResourceRef<'parent>,
174+
/// The other or their side of the merge operation.
175+
pub other: ResourceRef<'parent>,
176+
/// Which driver to use according to the resource's configuration,
177+
/// using the path of `current` to read git-attributes.
178+
pub driver: DriverChoice,
179+
/// Possibly processed options for use when performing the actual merge.
180+
///
181+
/// They may be inspected before the merge, or altered at will.
182+
pub options: platform::merge::Options,
183+
}

0 commit comments

Comments
 (0)