use std::collections::{BTreeMap, BTreeSet};
use std::num::NonZeroU64;
use super::labels::elements_in_order;
use super::{LOCK, Lock, Operation, Refusal, Relabel, Stopped};
use crate::bindings::Bindings;
use crate::codec::{LABELS, field};
use crate::conflict::{ConflictOwner, ConflictValue};
use crate::document::Document;
use crate::identity::{Key, Label};
use crate::ids::ElementId;
fn settled_path(path: &str) -> bool {
path == LOCK || is_bindings(path)
}
fn is_bindings(path: &str) -> bool {
path.strip_prefix("spec/") == Some(LABELS)
}
pub(super) fn preconditions(
stopped: &Stopped,
publish_branch: &str,
) -> Result<NonZeroU64, Refusal> {
let unmerged: Vec<String> = stopped
.unmerged
.iter()
.filter(|path| !settled_path(path))
.cloned()
.collect();
if !unmerged.is_empty() {
return Err(Refusal::Unmerged(unmerged));
}
if stopped.branch.as_deref() != Some(publish_branch) {
return Err(Refusal::Branch {
current: stopped.branch.clone(),
publish: publish_branch.to_owned(),
});
}
let other = stopped.operation.other_head();
let mut highest = None;
for (revision, bytes) in [("HEAD", &stopped.head_lock), (other, &stopped.other_lock)] {
if let Some(bytes) = bytes {
let lock = Lock::read(bytes).map_err(|error| Refusal::Lock {
revision,
message: error.to_string(),
})?;
highest = highest.max(Some(lock.publish_sequence));
}
}
Ok(highest.map_or(NonZeroU64::MIN, |sequence| sequence.saturating_add(1)))
}
#[derive(Debug, Default)]
pub(super) struct Settlement {
recorded: bool,
lost: Vec<(ElementId, Label)>,
pending: Vec<History>,
}
#[derive(Debug)]
struct History {
element: ElementId,
aliases: Vec<Label>,
withdrawn: Vec<Label>,
}
impl History {
fn labels(&self) -> impl Iterator<Item = Label> + '_ {
self.aliases.iter().chain(&self.withdrawn).copied()
}
fn attach(&self, bindings: &mut Bindings) {
for alias in &self.aliases {
let added = bindings.add_alias(self.element, *alias);
added.expect("a settled key is bound");
}
for withdrawn in &self.withdrawn {
let added = bindings.add_withdrawn(self.element, *withdrawn);
added.expect("a settled key is bound");
}
}
}
impl Settlement {
pub(super) fn unrecorded(&self, stopped: &Stopped) -> bool {
!self.recorded && stopped.unmerged.iter().any(|path| is_bindings(path))
}
pub(super) fn reattach(self, bindings: &mut Bindings) -> Vec<Relabel> {
for history in &self.pending {
history.attach(bindings);
}
let live = |element| bindings.label_of(element).expect("a settled key is bound");
self.lost
.into_iter()
.map(|(element, withdrawn)| Relabel {
element: element.key(),
withdrawn,
label: live(element),
})
.collect()
}
}
pub(super) fn settle(document: &mut Document, operation: Operation) -> Settlement {
let mut settlement = Settlement::default();
let Some(at) = document.conflicts.iter().position(|conflict| {
conflict.owner == ConflictOwner::Labels && conflict.field.as_str() == field::BINDINGS
}) else {
return settlement;
};
let conflict = document.conflicts.remove(at);
settlement.recorded = true;
let (pulled, other) = match operation {
Operation::Merge => (&conflict.theirs, &conflict.ours),
Operation::Rebase => (&conflict.ours, &conflict.theirs),
};
let (pulled, other) = (sides(pulled.as_ref()), sides(other.as_ref()));
let elements: BTreeMap<Key, ElementId> = elements_in_order(document)
.into_iter()
.map(|element| (element.key(), element))
.collect();
let disputed: BTreeSet<Key> = pulled.keys().chain(other.keys()).copied().collect();
let held = held(&document.bindings, &disputed, &pulled);
for key in &disputed {
let Some(&element) = elements.get(key) else {
continue;
};
document.bindings.unbind(element);
let outcome = settled(element, pulled.get(key), other.get(key), &held);
settlement.add(element, outcome, &mut document.bindings);
}
let held_back = settlement.pending.iter().flat_map(History::labels);
document.counters.raise_all(held_back);
settlement
}
impl Settlement {
fn add(&mut self, element: ElementId, outcome: Outcome, bindings: &mut Bindings) {
let lost = outcome.lost.iter().map(|label| (element, *label));
self.lost.extend(lost);
let history = outcome.history;
match outcome.live {
Some(live) => {
let bound = bindings.bind_as_written(element, live);
bound.expect("a disputed key was unbound");
history.attach(bindings);
}
None => self.pending.push(history),
}
}
}
struct Side {
key: Key,
label: Label,
aliases: Vec<Label>,
withdrawn: Vec<Label>,
}
impl Side {
fn labels(&self) -> impl Iterator<Item = Label> + '_ {
std::iter::once(self.label).chain(self.aliases.iter().copied())
}
}
fn sides(value: Option<&ConflictValue>) -> BTreeMap<Key, Side> {
let Some(ConflictValue::List(items)) = value else {
return BTreeMap::new();
};
items
.iter()
.filter_map(side)
.map(|side| (side.key, side))
.collect()
}
fn side(value: &ConflictValue) -> Option<Side> {
let ConflictValue::Record(fields) = value else {
return None;
};
let named = |name: &str| fields.iter().find(|(each, _)| each == name).map(|(_, v)| v);
let text = |name: &str| match named(name) {
Some(ConflictValue::Scalar(text)) => Some(text.as_str()),
_ => None,
};
let labels = |name: &str| match named(name) {
Some(ConflictValue::List(items)) => items
.iter()
.filter_map(|item| match item {
ConflictValue::Scalar(text) => Label::parse(text).ok(),
_ => None,
})
.collect(),
_ => Vec::new(),
};
Some(Side {
key: Key::parse(text(field::KEY)?).ok()?,
label: Label::parse(text(field::LABEL)?).ok()?,
aliases: labels(field::ALIASES),
withdrawn: labels(field::WITHDRAWN),
})
}
fn held(
bindings: &Bindings,
disputed: &BTreeSet<Key>,
pulled: &BTreeMap<Key, Side>,
) -> BTreeMap<Label, Key> {
let undisputed = bindings
.iter()
.filter(|binding| !disputed.contains(&binding.element.key()))
.flat_map(|binding| binding.labels().map(|label| (label, binding.element.key())));
let disputed = pulled
.values()
.flat_map(|side| side.labels().map(|label| (label, side.key)));
undisputed.chain(disputed).collect()
}
struct Outcome {
live: Option<Label>,
history: History,
lost: Vec<Label>,
}
fn settled(
element: ElementId,
pulled: Option<&Side>,
other: Option<&Side>,
held: &BTreeMap<Label, Key>,
) -> Outcome {
let key = element.key();
let collides = |label: Label| held.get(&label).is_some_and(|holder| *holder != key);
let (mut live, mut aliases) = pulled.map_or((None, Vec::new()), |side| {
(Some(side.label), side.aliases.clone())
});
let mut lost = Vec::new();
if let Some(other) = other {
if pulled.is_none() && !collides(other.label) {
live = Some(other.label);
}
for label in other.labels() {
if live == Some(label) || aliases.contains(&label) {
continue;
}
if collides(label) || label == other.label {
lost.push(label);
} else {
aliases.push(label);
}
}
}
let mut withdrawn: Vec<Label> = Vec::new();
let earlier = pulled
.into_iter()
.chain(other)
.flat_map(|side| &side.withdrawn);
for label in earlier.chain(&lost) {
if !withdrawn.contains(label) {
withdrawn.push(*label);
}
}
Outcome {
live,
history: History {
element,
aliases,
withdrawn,
},
lost,
}
}