mod conflict;
mod labels;
mod list;
pub mod order;
mod record;
mod same;
use std::fmt;
use crate::codec::schema::{Field, Item, Kind, Order, Shape};
use crate::codec::{Content, Mode, ReadError, Rec, Role, SpecPath, Val, read_file, write_content};
use crate::identity::Label;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Input {
Base,
Ours,
Theirs,
}
impl fmt::Display for Input {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::Base => "base",
Self::Ours => "ours",
Self::Theirs => "theirs",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Merged {
pub bytes: Vec<u8>,
pub conflicts: usize,
pub disputed: Vec<Label>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MergeError {
NotAFormatFile,
Unreadable(Vec<(Input, ReadError)>),
BothChanged,
Unwritable(ReadError),
}
pub fn merge_file(
path: &SpecPath,
base: &[u8],
ours: &[u8],
theirs: &[u8],
) -> Result<Merged, MergeError> {
let role = Role::of(path).ok_or(MergeError::NotAFormatFile)?;
let mut errors = Vec::new();
let mut read = |input: Input, bytes: &[u8]| {
if input == Input::Base && bytes.is_empty() && role != Role::Lint {
return None;
}
match read_file(path, bytes, Mode::Strict) {
Ok((_, content)) => Some(content),
Err(error) => {
errors.push((input, error));
None
}
}
};
let base = read(Input::Base, base);
let ours = read(Input::Ours, ours);
let theirs = read(Input::Theirs, theirs);
let (Some(ours), Some(theirs), true) = (ours, theirs, errors.is_empty()) else {
return Err(MergeError::Unreadable(errors));
};
let (content, disputed) = merge_content(base, ours, theirs)?;
let bytes = write_content(path, role, content);
let (_, written) = read_file(path, &bytes, Mode::Strict).map_err(MergeError::Unwritable)?;
Ok(Merged {
conflicts: written.records().map(count).sum(),
bytes,
disputed,
})
}
fn merge_content(
base: Option<Content>,
ours: Content,
theirs: Content,
) -> Result<(Content, Vec<Label>), MergeError> {
Ok(match (base, ours, theirs) {
(base, Content::Record(ours), Content::Record(theirs)) => {
let base = match base {
Some(Content::Record(base)) => Some(base),
_ => None,
};
if ours.kind == Kind::Labels {
let (record, disputed) = labels::merge(base.as_ref(), &ours, &theirs);
return Ok((Content::Record(record), disputed));
}
(
Content::Record(record::merge_host(base.as_ref(), &ours, &theirs)),
Vec::new(),
)
}
(base, Content::Registry(ours), Content::Registry(theirs)) => {
let base = match base {
Some(Content::Registry(base)) => base,
_ => Vec::new(),
};
(
Content::Registry(registry(&base, &ours, &theirs)),
Vec::new(),
)
}
(base, Content::Lint(ours), Content::Lint(theirs)) => {
let base = match base {
Some(Content::Lint(base)) => base,
_ => Vec::new(),
};
(Content::Lint(lint(&base, ours, theirs)?), Vec::new())
}
_ => return Err(MergeError::NotAFormatFile),
})
}
fn registry(base: &[Rec], ours: &[Rec], theirs: &[Rec]) -> Vec<Rec> {
let first = ours
.first()
.or_else(|| theirs.first())
.or_else(|| base.first());
let Some(kind) = first.map(|entry| entry.kind) else {
return Vec::new();
};
let entries = Field {
name: "entries",
required: false,
shape: Shape::List(Item::Record(kind)),
order: Order::ByKey,
supply: None,
};
let values =
|entries: &[Rec]| -> Vec<Val> { entries.iter().cloned().map(Val::Record).collect() };
let mut none = Vec::new();
let mut host = conflict::Host::root(&mut none);
let merged = list::merge_list(
&entries,
[&values(base), &values(ours), &values(theirs)],
&mut host,
);
list::records(merged)
}
fn lint(base: &[u8], ours: Vec<u8>, theirs: Vec<u8>) -> Result<Vec<u8>, MergeError> {
if ours == theirs || theirs == base {
Ok(ours)
} else if ours == base {
Ok(theirs)
} else {
Err(MergeError::BothChanged)
}
}
fn count(record: &Rec) -> usize {
record.conflicts.len() + record.children().map(count).sum::<usize>()
}