Skip to content

Commit a6f3e30

Browse files
committed
add all relevant tests for the merge processing pipeline
1 parent ad9587a commit a6f3e30

File tree

9 files changed

+1310
-215
lines changed

9 files changed

+1310
-215
lines changed

gix-merge/src/blob/mod.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,6 @@ pub struct Pipeline {
125125
pub filter: gix_filter::Pipeline,
126126
/// Options affecting the way we read files.
127127
pub options: pipeline::Options,
128-
/// All available merge drivers.
129-
///
130-
/// They are referenced in git-attributes by name, and we hand out indices into this array.
131-
drivers: Vec<Driver>,
132-
/// Pre-configured attributes to obtain additional merge-related information.
133-
attrs: gix_filter::attributes::search::Outcome,
134128
/// A buffer to produce disk-accessible paths from worktree roots.
135129
path: PathBuf,
136130
}
@@ -152,7 +146,14 @@ pub struct Platform {
152146
pub filter: Pipeline,
153147
/// A way to access `.gitattributes`
154148
pub attr_stack: gix_worktree::Stack,
155-
149+
/// Further configuration that affects the merge.
150+
pub options: platform::Options,
151+
/// All available merge drivers.
152+
///
153+
/// They are referenced in git-attributes by name, and we hand out indices into this array.
154+
drivers: Vec<Driver>,
155+
/// Pre-configured attributes to obtain additional merge-related information.
156+
attrs: gix_filter::attributes::search::Outcome,
156157
/// The way we convert resources into mergeable states.
157158
filter_mode: pipeline::Mode,
158159
}

gix-merge/src/blob/pipeline.rs

+76-172
Large diffs are not rendered by default.

gix-merge/src/blob/platform.rs

+134-34
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
use bstr::{BStr, BString};
2-
3-
use crate::blob::pipeline::DriverChoice;
4-
use crate::blob::{pipeline, Pipeline, Platform, ResourceKind};
1+
use crate::blob::{pipeline, BuiltinDriver, Pipeline, Platform, ResourceKind};
2+
use bstr::{BStr, BString, ByteSlice};
3+
use gix_filter::attributes;
54

65
/// A stored value representing a resource that participates in a merge.
76
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
@@ -10,8 +9,8 @@ pub(super) struct Resource {
109
id: gix_hash::ObjectId,
1110
/// The repository-relative path where the resource lives in the tree.
1211
rela_path: BString,
13-
/// The outcome of converting a resource into a diffable format using [Pipeline::convert_to_mergeable()].
14-
conversion: pipeline::Outcome,
12+
/// The outcome of converting a resource into a mergable format using [Pipeline::convert_to_mergeable()].
13+
data: Option<pipeline::Data>,
1514
/// The kind of the resource we are looking at. Only possible values are `Blob` and `BlobExecutable`.
1615
mode: gix_object::tree::EntryKind,
1716
/// A possibly empty buffer, depending on `conversion.data` which may indicate the data is considered binary
@@ -26,14 +25,51 @@ pub struct ResourceRef<'a> {
2625
pub data: resource::Data<'a>,
2726
/// The location of the resource, relative to the working tree.
2827
pub rela_path: &'a BStr,
29-
/// Which driver to use according to the resource's configuration.
30-
pub driver_choice: DriverChoice,
3128
/// The id of the content as it would be stored in `git`, or `null` if the content doesn't exist anymore at
3229
/// `rela_path` or if it was never computed. This can happen with content read from the worktree, which
3330
/// after its 'to-git' conversion never had its hash computed.
3431
pub id: &'a gix_hash::oid,
3532
}
3633

34+
/// Options for use in a [`Platform`].
35+
#[derive(Default, Clone, PartialEq, Eq, Debug, Hash, Ord, PartialOrd)]
36+
pub struct Options {
37+
/// Define which driver to use by name if the `merge` attribute for a resource is unspecified.
38+
///
39+
/// This is the value of the `merge.default` git configuration.
40+
pub default_driver: Option<BString>,
41+
}
42+
43+
/// The selection of the driver to use by a resource obtained with [`Pipeline::convert_to_mergeable()`].
44+
///
45+
/// If available, an index into the `drivers` field to access more diff-related information of the driver for items
46+
/// at the given path, as previously determined by git-attributes.
47+
///
48+
/// * `merge` is set
49+
/// - Use the [`BuiltinDriver::Text`]
50+
/// * `-merge` is unset
51+
/// - Use the [`BuiltinDriver::Binary`]
52+
/// * `!merge` is unspecified
53+
/// - Use [`Options::default_driver`] or [`BuiltinDriver::Text`].
54+
/// * `merge=name`
55+
/// - Search for a user-configured or built-in driver called `name`.
56+
/// - If not found, silently default to [`BuiltinDriver::Text`]
57+
///
58+
/// Note that drivers are queried even if there is no object available.
59+
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
60+
pub enum DriverChoice {
61+
/// Use the given built-in driver to perform the merge.
62+
BuiltIn(BuiltinDriver),
63+
/// Use the user-provided driver program using the index into [the pipelines driver array](Pipeline::drivers().
64+
Index(usize),
65+
}
66+
67+
impl Default for DriverChoice {
68+
fn default() -> Self {
69+
DriverChoice::BuiltIn(Default::default())
70+
}
71+
}
72+
3773
///
3874
pub mod resource {
3975
use crate::blob::{
@@ -44,11 +80,10 @@ pub mod resource {
4480
impl<'a> ResourceRef<'a> {
4581
pub(super) fn new(cache: &'a Resource) -> Self {
4682
ResourceRef {
47-
data: cache.conversion.data.map_or(Data::Missing, |data| match data {
83+
data: cache.data.map_or(Data::Missing, |data| match data {
4884
pipeline::Data::Buffer => Data::Buffer(&cache.buffer),
49-
pipeline::Data::Binary { size } => Data::Binary { size },
85+
pipeline::Data::TooLarge { size } => Data::Binary { size },
5086
}),
51-
driver_choice: cache.conversion.driver,
5287
rela_path: cache.rela_path.as_ref(),
5388
id: &cache.id,
5489
}
@@ -118,7 +153,7 @@ pub mod set_resource {
118153

119154
///
120155
pub mod merge {
121-
use crate::blob::pipeline::DriverChoice;
156+
use crate::blob::platform::DriverChoice;
122157
use crate::blob::platform::ResourceRef;
123158
use crate::blob::{builtin_driver, BuiltinDriver, Driver, Resolution};
124159
use bstr::BString;
@@ -135,6 +170,9 @@ pub mod merge {
135170
pub ancestor: ResourceRef<'parent>,
136171
/// The other or their side of the merge operation.
137172
pub other: ResourceRef<'parent>,
173+
/// Which driver to use according to the resource's configuration,
174+
/// using the path of `current` to read git-attributes.
175+
pub driver_choice: DriverChoice,
138176
}
139177

140178
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -267,9 +305,9 @@ pub mod merge {
267305
/// Return the configured driver program for use with [`Self::prepare_external_driver()`], or `Err`
268306
/// with the built-in driver to use instead.
269307
pub fn configured_driver(&self) -> Result<&'parent Driver, BuiltinDriver> {
270-
match self.current.driver_choice {
308+
match self.driver_choice {
271309
DriverChoice::BuiltIn(builtin) => Err(builtin),
272-
DriverChoice::Index(idx) => self.parent.filter.drivers.get(idx).ok_or(BuiltinDriver::default()),
310+
DriverChoice::Index(idx) => self.parent.drivers.get(idx).ok_or(BuiltinDriver::default()),
273311
}
274312
}
275313
}
@@ -299,14 +337,21 @@ pub mod merge {
299337

300338
///
301339
pub mod prepare_merge {
340+
use crate::blob::ResourceKind;
341+
use bstr::BString;
342+
302343
/// The error returned by [Platform::prepare_merge()](super::Platform::prepare_merge_state()).
303344
#[derive(Debug, thiserror::Error)]
304345
#[allow(missing_docs)]
305346
pub enum Error {
306347
#[error("The 'current', 'ancestor' or 'other' resource for the merge operation were not set")]
307348
UnsetResource,
308-
#[error("Tried to merge 'current' and 'other' where at least one of them is removed")]
309-
CurrentOrOtherRemoved,
349+
#[error("Failed to obtain attributes for {kind:?} resource at '{rela_path}'")]
350+
Attributes {
351+
rela_path: BString,
352+
kind: ResourceKind,
353+
source: std::io::Error,
354+
},
310355
}
311356
}
312357

@@ -315,18 +360,44 @@ impl Platform {
315360
/// Create a new instance with a way to `filter` data from the object database and turn it into something that is merge-able.
316361
/// `filter_mode` decides how to do that specifically.
317362
/// Use `attr_stack` to access attributes pertaining worktree filters and merge settings.
318-
pub fn new(filter: Pipeline, filter_mode: pipeline::Mode, attr_stack: gix_worktree::Stack) -> Self {
363+
/// `drivers` are the list of available merge drivers that individual paths can refer to by means of git attributes.
364+
/// `options` further configure the operation.
365+
pub fn new(
366+
filter: Pipeline,
367+
filter_mode: pipeline::Mode,
368+
attr_stack: gix_worktree::Stack,
369+
mut drivers: Vec<super::Driver>,
370+
options: Options,
371+
) -> Self {
372+
drivers.sort_by(|a, b| a.name.cmp(&b.name));
319373
Platform {
374+
drivers,
320375
current: None,
321376
ancestor: None,
322377
other: None,
323378
filter,
324379
filter_mode,
325380
attr_stack,
381+
attrs: {
382+
let mut out = attributes::search::Outcome::default();
383+
out.initialize_with_selection(&Default::default(), Some("merge"));
384+
out
385+
},
386+
options,
326387
}
327388
}
328389
}
329390

391+
/// Access
392+
impl Platform {
393+
/// Return all drivers that this instance was initialized with.
394+
///
395+
/// They are sorted by [`name`](super::Driver::name) to support binary searches.
396+
pub fn drivers(&self) -> &[super::Driver] {
397+
&self.drivers
398+
}
399+
}
400+
330401
/// Preparation
331402
impl Platform {
332403
/// Store enough information about a resource to eventually use it in a merge, where…
@@ -351,33 +422,62 @@ impl Platform {
351422
self.set_resource_inner(id, mode, rela_path, kind, objects)
352423
}
353424

354-
/// Returns the resource of the given kind if it was set.
355-
pub fn resource(&self, kind: ResourceKind) -> Option<ResourceRef<'_>> {
356-
let cache = match kind {
357-
ResourceKind::CurrentOrOurs => self.current.as_ref(),
358-
ResourceKind::CommonAncestorOrBase => self.ancestor.as_ref(),
359-
ResourceKind::OtherOrTheirs => self.other.as_ref(),
360-
}?;
361-
ResourceRef::new(cache).into()
362-
}
363-
364425
/// Prepare all state needed for performing a merge, using all [previously set](Self::set_resource()) resources.
365-
pub fn prepare_merge_state(&self) -> Result<merge::State<'_>, prepare_merge::Error> {
426+
/// Note that no additional validation is performed here to facilitate inspection.
427+
pub fn prepare_merge_state(
428+
&mut self,
429+
objects: &impl gix_object::Find,
430+
) -> Result<merge::State<'_>, prepare_merge::Error> {
366431
let current = self.current.as_ref().ok_or(prepare_merge::Error::UnsetResource)?;
367432
let ancestor = self.ancestor.as_ref().ok_or(prepare_merge::Error::UnsetResource)?;
368433
let other = self.other.as_ref().ok_or(prepare_merge::Error::UnsetResource)?;
369434

435+
let entry = self
436+
.attr_stack
437+
.at_entry(current.rela_path.as_bstr(), None, objects)
438+
.map_err(|err| prepare_merge::Error::Attributes {
439+
source: err,
440+
kind: ResourceKind::CurrentOrOurs,
441+
rela_path: current.rela_path.clone(),
442+
})?;
443+
entry.matching_attributes(&mut self.attrs);
444+
let attr = self.attrs.iter_selected().next().expect("pre-initialized with 'diff'");
445+
let driver = match attr.assignment.state {
446+
attributes::StateRef::Set => DriverChoice::BuiltIn(BuiltinDriver::Text),
447+
attributes::StateRef::Unset => DriverChoice::BuiltIn(BuiltinDriver::Binary),
448+
attributes::StateRef::Value(_) | attributes::StateRef::Unspecified => {
449+
let name = match attr.assignment.state {
450+
attributes::StateRef::Value(name) => Some(name.as_bstr()),
451+
attributes::StateRef::Unspecified => {
452+
self.options.default_driver.as_ref().map(|name| name.as_bstr())
453+
}
454+
_ => unreachable!("only value and unspecified are possible here"),
455+
};
456+
name.and_then(|name| {
457+
self.drivers
458+
.binary_search_by(|d| d.name.as_bstr().cmp(name))
459+
.ok()
460+
.map(DriverChoice::Index)
461+
.or_else(|| {
462+
name.to_str()
463+
.ok()
464+
.and_then(BuiltinDriver::by_name)
465+
.map(DriverChoice::BuiltIn)
466+
})
467+
})
468+
.unwrap_or_default()
469+
}
470+
};
471+
370472
let out = merge::State {
371473
parent: self,
474+
driver_choice: driver,
372475
current: ResourceRef::new(current),
373476
ancestor: ResourceRef::new(ancestor),
374477
other: ResourceRef::new(other),
375478
};
376479

377-
match (current.conversion.data, other.conversion.data) {
378-
(None, None) => Err(prepare_merge::Error::CurrentOrOtherRemoved),
379-
(_, _) => Ok(out),
380-
}
480+
Ok(out)
381481
}
382482
}
383483

@@ -430,15 +530,15 @@ impl Platform {
430530
*storage = Some(Resource {
431531
id,
432532
rela_path: rela_path.to_owned(),
433-
conversion: out,
533+
data: out,
434534
mode,
435535
buffer: buf_storage,
436536
});
437537
}
438538
Some(storage) => {
439539
storage.id = id;
440540
storage.rela_path = rela_path.to_owned();
441-
storage.conversion = out;
541+
storage.data = out;
442542
storage.mode = mode;
443543
}
444544
};
Binary file not shown.
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env bash
2+
set -eu -o pipefail
3+
4+
git init -q
5+
6+
echo a > a
7+
echo b > b
8+
echo union > union
9+
echo e > e-no-attr
10+
echo unset > unset
11+
echo unspecified > unspecified
12+
13+
cat <<EOF >.gitattributes
14+
a merge=a
15+
b merge=b
16+
union merge=union
17+
missing merge=missing
18+
unset -merge
19+
unspecified !merge
20+
EOF
21+
22+
git add . && git commit -m "init"

gix-merge/tests/merge/blob/builtin_driver.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,8 @@ mod text {
123123
"Number of expected diverging cases must match the actual one - probably the implementation improved"
124124
);
125125
assert_eq!(
126-
(num_diverging as f32 / num_cases as f32) * 100.0,
127-
12.053572,
126+
((num_diverging as f32 / num_cases as f32) * 100.0) as usize,
127+
12,
128128
"Just to show the percentage of skipped tests - this should get better"
129129
);
130130
Ok(())

0 commit comments

Comments
 (0)