Skip to main content

ical/tree/
merge.rs

1//! # Three-way merge
2//!
3//! Reconcile two divergent edits of a calendar against their common base.
4//!
5//! [`IcalMerge::merge`] is the unit a synchronisation engine needs: given a
6//! base calendar and two calendars derived from it, it reports what each side
7//! changed relative to the base and builds one merged calendar.
8//!
9//! Never last-writer-wins: a field only one side touched is taken from that
10//! side, and a field both sides touched is a conflict, reported so a caller
11//! can resolve it differently.
12//!
13//! The merged calendar starts as a clone of the left one, so the left side's
14//! bytes are there exactly as they were, folds included; the right side's
15//! actions are then replayed line by line, so every line the right side did
16//! not touch keeps its bytes too.
17//!
18//! ## Ours and theirs
19//!
20//! The left side is git's `ours` and the right side is git's `theirs`. The
21//! left side supplies the baseline, so its folding, its parameter casing and
22//! its property order come out untouched, and it keeps its own value where
23//! both sides wrote one into a single field.
24//!
25//! One side answers both questions on purpose. A caller reaches for a merge
26//! holding a version it is merging into, and that version is the one it would
27//! rather not churn and the one it means to keep.
28//!
29//! Every collision is reported either way, so a caller wanting the other value
30//! puts it to somebody rather than asking the merge to guess.
31//!
32//! ## The four steps
33//!
34//! `node` addresses a calendar: it walks it into components, each carrying the
35//! `UID` and `RECURRENCE-ID` path that names it, and finds again in one
36//! calendar what was read in another.
37//!
38//! `diff` matches a side against the base and reports one change per field,
39//! down to a single list item or parameter. `compare` says when two sides
40//! performed one act, which is only where they wrote the same bytes.
41//!
42//! `judge` decides whether the right side's act lands and what to report about
43//! it, and `replay` puts the ones that land onto the left side's bytes.
44//!
45//! ## The two ways a merge can conflict
46//!
47//! **Divergence.** Both sides changed the same field. The left side's outcome
48//! is kept, except where a removal meets an update: there the update wins
49//! whichever side it came from, because keeping data beats losing it silently.
50//!
51//! **Recurrence.** One side changed what defines the series (its `DTSTART`,
52//! `DTEND`, `DURATION`, `RRULE`, `RDATE` or `EXDATE`, or the series component
53//! itself) while the other changed one instance of it.
54//!
55//! Neither is wrong and both survive, but a rule that moved may have moved the
56//! ground the override stood on, so it is reported. A change to anything else
57//! the series carries cannot have moved an occurrence and is not reported
58//! against one.
59
60mod compare;
61mod diff;
62mod judge;
63mod node;
64mod op;
65mod replay;
66
67use alloc::{borrow::Cow, vec::Vec};
68
69use crate::{
70    param::IcalParam,
71    tree::{
72        cst::IcalCst,
73        merge::{
74            diff::Diff,
75            op::{Op, Slot},
76            replay::{Restored, Shift},
77        },
78    },
79    value::IcalValue,
80};
81
82/// A three-way merge waiting to run.
83///
84/// See the module documentation for the matching, granularity and conflict
85/// rules.
86pub struct IcalMerge<'m, 'a> {
87    /// The common ancestor both sides were derived from.
88    pub base: &'m IcalCst<'a>,
89    /// The side being merged into, git's `ours`. The merged calendar is built
90    /// from its bytes, and a collision neither side settles keeps its value.
91    pub left: &'m IcalCst<'a>,
92    /// The side being merged in, git's `theirs`. Its changes are replayed onto
93    /// the left's bytes.
94    pub right: &'m IcalCst<'a>,
95}
96
97impl<'a> IcalMerge<'_, 'a> {
98    /// Run the merge.
99    pub fn merge(self) -> IcalMergeReport<'a> {
100        let version = self.base.version();
101
102        let base = self.base.nodes();
103        let left = self.left.nodes();
104        let right = self.right.nodes();
105
106        let left_ops = Diff {
107            base: &base,
108            side: &left,
109            version,
110        }
111        .run();
112        let right_ops = Diff {
113            base: &base,
114            side: &right,
115            version,
116        }
117        .run();
118
119        let mut merged = self.left.clone();
120        let mut conflicts = Vec::new();
121        let mut applicable = Vec::new();
122
123        for op in &right_ops {
124            let verdict = self.judge(op, &left_ops, &right_ops);
125
126            if verdict.applies {
127                applicable.push(op);
128            }
129
130            if let Some(left) = verdict.reason {
131                conflicts.push(IcalMergeConflict {
132                    left,
133                    right: op.action.clone(),
134                });
135            }
136        }
137
138        applicable.sort_by_key(|op| op.replay_order());
139
140        let shift = Shift::of(&left_ops);
141        let mut restored = Restored::default();
142
143        for op in applicable {
144            self.apply(&mut merged, op, &shift, &mut restored);
145        }
146
147        IcalMergeReport {
148            merged,
149            left: left_ops.into_iter().map(|op| op.action).collect(),
150            right: right_ops.into_iter().map(|op| op.action).collect(),
151            conflicts,
152        }
153    }
154}
155
156/// The outcome of a three-way merge.
157#[derive(Clone, Debug)]
158pub struct IcalMergeReport<'a> {
159    /// The merged calendar: the left one with the right side's applicable
160    /// actions replayed onto it, line by line.
161    pub merged: IcalCst<'a>,
162    /// What the left calendar changed relative to the base.
163    pub left: Vec<IcalMergeAction<'a>>,
164    /// What the right calendar changed relative to the base.
165    pub right: Vec<IcalMergeAction<'a>>,
166    /// The pairs of actions that collided, one per side.
167    pub conflicts: Vec<IcalMergeConflict<'a>>,
168}
169
170/// Two actions that collided, one per side.
171///
172/// They are named `left` and `right` as vcard-rs names its own pair, the left
173/// one carrying with it why the right one did not simply apply.
174#[derive(Clone, Debug, PartialEq, Eq)]
175pub struct IcalMergeConflict<'a> {
176    /// The left side's action, and why the right one did not simply apply.
177    pub left: IcalMergeReason<'a>,
178    /// The action the right side wanted.
179    pub right: IcalMergeAction<'a>,
180}
181
182/// A left-side action, and why the right one did not simply apply against it.
183#[derive(Clone, Debug, PartialEq, Eq)]
184pub enum IcalMergeReason<'a> {
185    /// Both sides changed the same field. The merged calendar holds the left
186    /// side's outcome, except where a removal met an update, in which case the
187    /// update was kept whichever side it came from.
188    Divergent(IcalMergeAction<'a>),
189    /// One side changed a series and the other changed one of its instances.
190    /// Both survive in the merged calendar; a rule that moved may have moved
191    /// the ground the override stood on, which is why this is said out loud.
192    Recurrence(IcalMergeAction<'a>),
193}
194
195/// One component's address: the steps from the calendar root down to it.
196#[derive(Clone, Debug, Default, PartialEq, Eq)]
197pub struct IcalComponentPath<'a>(pub Vec<IcalComponentStep<'a>>);
198
199/// One step of a component path: a name and the identity that tells it from
200/// its same-named siblings.
201#[derive(Clone, Debug, PartialEq, Eq)]
202pub struct IcalComponentStep<'a> {
203    /// The component name, uppercase.
204    pub name: Cow<'a, str>,
205    /// The `UID`, with the `RECURRENCE-ID` after a solidus when the component
206    /// overrides one instance; the position among same-named siblings when the
207    /// component carries no `UID`.
208    pub key: Cow<'a, str>,
209}
210
211/// One property's address: the component holding it, its name, and what tells
212/// it from the component's other properties of that name.
213#[derive(Clone, Debug, PartialEq, Eq)]
214pub struct IcalPropPath<'a> {
215    /// The component the property belongs to.
216    pub component: IcalComponentPath<'a>,
217    /// The property name as written.
218    pub name: Cow<'a, str>,
219    /// The position among the component's properties of that name, counted in
220    /// the calendar the action was read from.
221    pub index: usize,
222    /// The value that tells the property from its same-named siblings.
223    ///
224    /// Where iCalendar gives it one: the calendar user address of an
225    /// `ATTENDEE`, the URI or inline binary of an `ATTACH`, the `UID` a
226    /// `RELATED-TO` points at, the URI of a `CONFERENCE` or an `IMAGE`.
227    /// Lowercased, since matching normalises and writing is exact.
228    ///
229    /// `None` for every other property, whose position then tells it from its
230    /// siblings, and `None` too for a value a same-named sibling repeats,
231    /// which tells neither of them apart.
232    pub identity: Option<Cow<'a, str>>,
233}
234
235/// One change a side made relative to the base.
236#[derive(Clone, Debug, PartialEq, Eq)]
237pub enum IcalMergeAction<'a> {
238    /// A component the side added.
239    ComponentAdded {
240        /// Where it was added.
241        at: IcalComponentPath<'a>,
242    },
243    /// A component the side removed.
244    ComponentRemoved {
245        /// What it removed.
246        at: IcalComponentPath<'a>,
247    },
248    /// A property the side added.
249    PropAdded {
250        /// Where it was added.
251        at: IcalPropPath<'a>,
252        /// The added value.
253        value: IcalValue<'a>,
254    },
255    /// A property the side removed.
256    PropRemoved {
257        /// What it removed.
258        at: IcalPropPath<'a>,
259        /// The removed value.
260        value: IcalValue<'a>,
261    },
262    /// A matched property whose value changed.
263    ValueChanged {
264        /// The changed property.
265        at: IcalPropPath<'a>,
266        /// The base value.
267        old: IcalValue<'a>,
268        /// The changed value.
269        new: IcalValue<'a>,
270    },
271    /// One item joined a list value (`CATEGORIES`, `RDATE`, `EXDATE`).
272    ValueItemAdded {
273        /// The changed property.
274        at: IcalPropPath<'a>,
275        /// The added item.
276        item: Cow<'a, str>,
277    },
278    /// One item left a list value.
279    ValueItemRemoved {
280        /// The changed property.
281        at: IcalPropPath<'a>,
282        /// The removed item.
283        item: Cow<'a, str>,
284    },
285    /// A parameter the side added.
286    ParamAdded {
287        /// The changed property.
288        at: IcalPropPath<'a>,
289        /// The added parameter.
290        param: IcalParam<'a>,
291    },
292    /// A parameter the side removed.
293    ParamRemoved {
294        /// The changed property.
295        at: IcalPropPath<'a>,
296        /// The removed parameter.
297        param: IcalParam<'a>,
298    },
299    /// A parameter whose value changed.
300    ParamChanged {
301        /// The changed property.
302        at: IcalPropPath<'a>,
303        /// The base parameter.
304        old: IcalParam<'a>,
305        /// The changed parameter.
306        new: IcalParam<'a>,
307    },
308}
309
310impl<'a> IcalMergeAction<'a> {
311    /// Whether the action takes something away.
312    fn is_removal(&self) -> bool {
313        matches!(
314            self,
315            Self::ComponentRemoved { .. }
316                | Self::PropRemoved { .. }
317                | Self::ValueItemRemoved { .. }
318                | Self::ParamRemoved { .. }
319        )
320    }
321
322    /// The property the action lands on, for the actions that land on one.
323    fn prop_path(&self) -> Option<&IcalPropPath<'a>> {
324        match self {
325            Self::ComponentAdded { .. } | Self::ComponentRemoved { .. } => None,
326            Self::PropAdded { at, .. }
327            | Self::PropRemoved { at, .. }
328            | Self::ValueChanged { at, .. }
329            | Self::ValueItemAdded { at, .. }
330            | Self::ValueItemRemoved { at, .. }
331            | Self::ParamAdded { at, .. }
332            | Self::ParamRemoved { at, .. }
333            | Self::ParamChanged { at, .. } => Some(at),
334        }
335    }
336}