data/MergePlan.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QList> | ||
| 4 | #include <QString> | ||
| 5 | #include <QStringList> | ||
| 6 | |||
| 7 | #include "data/TextMerge3.h" | ||
| 8 | |||
| 9 | // The generic plan of a three-way merge (Sprint 107 S107.2.1, §5.2). | ||
| 10 | // | ||
| 11 | // Every merge engine (ICalMergePlan, VCardMergePlan, SieveMergePlan, | ||
| 12 | // SettingsMergePlan) splits into analyze(), which describes each mergeable | ||
| 13 | // item with its three values and who changed it, and apply(), which renders | ||
| 14 | // bytes from a plan plus the choices made on it. The old merge() functions | ||
| 15 | // are apply(analyze()) with the default choices; the resolver UI changes | ||
| 16 | // choices in between. | ||
| 17 | // | ||
| 18 | // Item identity (`id`) is stable for the same input: it names the | ||
| 19 | // component and the property or value ("VEVENT:uid:rid::SUMMARY", | ||
| 20 | // "SET:EMAIL\x1fkim@example\x1fwork"), never a position. `carryChoices` | ||
| 21 | // relies on it (D-9). | ||
| 22 | // | ||
| 23 | // Invariants (unit + fuzz, gate G4): | ||
| 24 | // - defaultChoice(state) never picks a value that neither side wrote: | ||
| 25 | // LocalOnly → Local, ServerOnly → Server, Same/BothSame → Local, | ||
| 26 | // Conflict → None. | ||
| 27 | // - openConflicts() counts every row the user must still decide; a | ||
| 28 | // coupled group (D-4) counts once unless its members are chosen | ||
| 29 | // separately. | ||
| 30 | // - Nothing in this unit parses or renders domain bytes. | ||
| 31 | namespace Merge { | ||
| 32 | |||
| 33 | // One side's value of one item. | ||
| 34 | struct Value { | ||
| 35 | bool present = false; | ||
| 36 | QString text; // the raw lines / bytes of this item on that side | ||
| 37 | }; | ||
| 38 | |||
| 39 | enum class Kind { | ||
| 40 | Scalar, // one property (title, start time, priority …) | ||
| 41 | SetValue, // one value of a multi-valued property (attendee, e-mail …) | ||
| 42 | Text, // long text chosen per line hunk (notes, a whole script) | ||
| 43 | Component, // a sub-component present on one side (occurrence, rule block) | ||
| 44 | Order, // the order of blocks (Sieve) | ||
| 45 | Deletion, // removed on one side, changed on the other | ||
| 46 | }; | ||
| 47 | |||
| 48 | enum class State { | ||
| 49 | Same, // all three equal (or unchanged on both sides) | ||
| 50 | LocalOnly, // only this computer changed it | ||
| 51 | ServerOnly, // only the server changed it | ||
| 52 | BothSame, // both changed it the same way | ||
| 53 | Conflict, // both changed it differently | ||
| 54 | }; | ||
| 55 | |||
| 56 | enum class Choice { | ||
| 57 | None, // open conflict | ||
| 58 | Local, // the value on this computer | ||
| 59 | Server, // the value on the server | ||
| 60 | Both, // set values and hunks: keep both sides' additions | ||
| 61 | Base, // the value both started from | ||
| 62 | Edited, // text: hunk choices and/or manual edits (see Item::edited) | ||
| 63 | }; | ||
| 64 | |||
| 65 | struct Item { | ||
| 66 | QString id; | ||
| 67 | QString name; // machine label: property name, block name, settings key | ||
| 68 | QString group; // coupled group id ("time", "completion", …) or "" | ||
| 69 | QString parent; // component item id of a nested row, or "" | ||
| 70 | QString context; // domain note, e.g. the RECURRENCE-ID of an occurrence | ||
| 71 | Kind kind = Kind::Scalar; | ||
| 72 | Value base; | ||
| 73 | Value local; | ||
| 74 | Value server; | ||
| 75 | State state = State::Same; | ||
| 76 | Choice choice = Choice::None; | ||
| 77 | bool touched = false; // chosen by the user (D-9 carry-over) | ||
| 78 | // Kind::Text: the line hunks against the base and one choice per hunk | ||
| 79 | // (Choice::None for an open conflicting hunk). `edited` (non-null) | ||
| 80 | // replaces the hunk result by text the user typed. | ||
| 81 | TextMerge::Result hunks; | ||
| 82 | QList<Choice> hunkChoices; | ||
| 83 | QString edited; | ||
| 84 | // Kind::Text: the plain text of each side the hunks were computed from | ||
| 85 | // (an iCalendar DESCRIPTION unescaped); `Value::text` stays the raw line. | ||
| 86 | QString basePlain; | ||
| 87 | QString localPlain; | ||
| 88 | QString serverPlain; | ||
| 89 | }; | ||
| 90 | |||
| 91 | struct ValidationProblem { | ||
| 92 | QString rule; // "event-end-before-start", "task-completion", … | ||
| 93 | QStringList itemIds; | ||
| 94 | QString message; // English; the resolver translates through tr() | ||
| 95 | }; | ||
| 96 | |||
| 97 | struct Plan { | ||
| 98 | QString domain; // "ical", "vcard", "sieve", "settings" | ||
| 99 | QList<Item> items; | ||
| 100 | bool comparable = true; // false: only a raw text merge is possible (D-12) | ||
| 101 | QString error; // why it is not comparable | ||
| 102 | QStringList separatedGroups; // coupled groups the user chooses per item | ||
| 103 | // The three inputs; apply() renders from them and the choices. | ||
| 104 | QString baseText; | ||
| 105 | QString localText; | ||
| 106 | QString serverText; | ||
| 107 | |||
| 108 | int indexOf(const QString &id) const; | ||
| 109 | const Item *item(const QString &id) const; | ||
| 110 | // Rows the user must still decide (open Kind::Text hunks included). | ||
| 111 | int openConflicts() const; | ||
| 112 | // Items that changed and merged without a question (D-5). | ||
| 113 | int combinedCount() const; | ||
| 114 | int conflictCount() const; // items in State::Conflict (decided or not) | ||
| 115 | 21624 | bool groupSeparated(const QString &group) const { return separatedGroups.contains(group); } | |
| 116 | // A coupled group whose members belong to different components (Sprint | ||
| 117 | // 107 J3: "occurrence:<id>" = an occurrence and the master's EXDATE value); | ||
| 118 | // every other group stays inside one parent. | ||
| 119 |
1/1✓ Branch 3 → 4 taken 147174 times.
|
147174 | static bool crossParentGroup(const QString &group) { return group.startsWith(QLatin1String("occurrence:")); } |
| 120 | }; | ||
| 121 | |||
| 122 | Choice defaultChoice(State state); | ||
| 123 | // The state of three values: unchanged / one side / both alike / conflict. | ||
| 124 | State stateOf(const Value &base, const Value &local, const Value &server); | ||
| 125 | // Sets every item's choice to its default (fresh plan). | ||
| 126 | void applyDefaults(Plan *plan); | ||
| 127 | // The user picks `choice` for `id`; members of a coupled group follow | ||
| 128 | // unless the group is separated. Returns false for an unknown id or a | ||
| 129 | // choice the item kind does not offer. | ||
| 130 | bool choose(Plan *plan, const QString &id, Choice choice); | ||
| 131 | // Picks Local (or Server) for every item: "Keep all mine" / "Take all | ||
| 132 | // from the server" (D-6). Text items follow as whole values. | ||
| 133 | void chooseAll(Plan *plan, Choice side); | ||
| 134 | // Text items: the choice of one hunk, or the user's text. | ||
| 135 | bool chooseHunk(Plan *plan, const QString &id, int hunk, Choice choice); | ||
| 136 | bool editText(Plan *plan, const QString &id, const QString &text); | ||
| 137 | // The value an item contributes to the result; `*present` false when the | ||
| 138 | // chosen side has no value (removed). Open conflicts resolve to the local | ||
| 139 | // value so a preview always renders. | ||
| 140 | QString resultText(const Item &item, bool *present = nullptr); | ||
| 141 | bool itemOpen(const Plan &plan, const Item &item); | ||
| 142 | // When every item's result equals one side's value: Local or Server, else | ||
| 143 | // None. The resolver then writes that side's bytes unchanged (G2). | ||
| 144 | Choice uniformSide(const Plan &plan); | ||
| 145 | // D-9: copies the user's choices of `previous` onto `next` for every item | ||
| 146 | // whose id exists in both and whose local and server values are unchanged. | ||
| 147 | // Returns the number of choices carried over. | ||
| 148 | int carryChoices(const Plan &previous, Plan *next); | ||
| 149 | |||
| 150 | const char *kindName(Kind kind); | ||
| 151 | const char *stateName(State state); | ||
| 152 | const char *choiceName(Choice choice); | ||
| 153 | Choice choiceFromName(const QString &name); | ||
| 154 | |||
| 155 | } // namespace Merge | ||
| 156 |