MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 93.0% 385 / 0 / 414
Functions: 96.8% 30 / 0 / 31
Branches: 84.3% 515 / 0 / 611

data/VCardMergePlan.cpp
Line Branch Exec Source
1 #include "VCardMergePlan.h"
2
3 #include "VCardCodec.h"
4
5 #include <QDateTime>
6 #include <QSet>
7
8 namespace VCardMergePlan {
9
10 namespace {
11
12 const QSet<QString> kMultiValued{QStringLiteral("EMAIL"), QStringLiteral("TEL"), QStringLiteral("ADR"),
13 QStringLiteral("URL"), QStringLiteral("IMPP"), QStringLiteral("RELATED"),
14 QStringLiteral("MEMBER"), QStringLiteral("X-ADDRESSBOOKSERVER-MEMBER"),
15 QStringLiteral("X-SOCIALPROFILE"), QStringLiteral("KEY"), QStringLiteral("LANG"),
16 QStringLiteral("LABEL"),
17 // Sprint 103 S103.1.7: people details merge as sets too.
18 QStringLiteral("X-ABRELATEDNAMES"), QStringLiteral("X-ABDATE"),
19 QStringLiteral("SOCIALPROFILE"), QStringLiteral("INTEREST"),
20 QStringLiteral("X-MAILJD-INTEREST"), QStringLiteral("X-MAILJD-LANG"),
21 QStringLiteral("HOBBY"), QStringLiteral("EXPERTISE"), QStringLiteral("X-MAILJD-MAIL")};
22 const QSet<QString> kFromTheirs{QStringLiteral("VERSION"), QStringLiteral("UID"), QStringLiteral("PRODID"),
23 QStringLiteral("REV")};
24
25 // Label lines of a property group (X-ABLabel, X-ABADR, …). X-ABDATE and
26 // X-ABRELATEDNAMES are values of their own (Sprint 103), not companions.
27 9845 bool isCompanion(const ICal::Property &property) {
28
3/3
✓ Branch 2 → 3 taken 9845 times.
✓ Branch 5 → 6 taken 9356 times.
✓ Branch 5 → 7 taken 489 times.
9845 if (VCardCodec::groupOf(property).isEmpty())
29 9356 return false;
30
1/1
✓ Branch 7 → 8 taken 489 times.
489 const QString name = VCardCodec::baseName(property);
31
5/5
✓ Branch 9 → 10 taken 489 times.
✓ Branch 10 → 11 taken 272 times.
✓ Branch 10 → 18 taken 217 times.
✓ Branch 13 → 14 taken 245 times.
✓ Branch 13 → 18 taken 27 times.
734 return name.startsWith(QLatin1String("X-AB")) && name != QLatin1String("X-ABDATE") &&
32
1/2
✓ Branch 16 → 17 taken 245 times.
✗ Branch 16 → 18 not taken.
245 name != QLatin1String("X-ABRELATEDNAMES");
33 489 }
34
35 // A property with the label lines of its group.
36 struct Unit {
37 QString name;
38 QString key; // multi-valued: name + value + types; single: name
39 QString text; // comparable rendering (group names stripped)
40 QList<ICal::Property> lines;
41 };
42
43 8467 QString withoutGroup(const ICal::Property &property) {
44
1/1
✓ Branch 2 → 3 taken 8467 times.
8467 const QString line = property.contentLine();
45
1/1
✓ Branch 3 → 4 taken 8467 times.
8467 const QString group = VCardCodec::groupOf(property);
46
3/3
✓ Branch 5 → 6 taken 8245 times.
✓ Branch 5 → 7 taken 222 times.
✓ Branch 8 → 9 taken 222 times.
16934 return group.isEmpty() ? line : line.mid(group.size() + 1);
47 8467 }
48
49 977 QString normalizedValue(const QString &name, const ICal::Property &property) {
50
2/2
✓ Branch 2 → 3 taken 977 times.
✓ Branch 3 → 4 taken 977 times.
977 QString value = ICal::unescapeText(property.value).trimmed();
51
2/2
✓ Branch 7 → 8 taken 481 times.
✓ Branch 7 → 10 taken 496 times.
977 if (name == QLatin1String("EMAIL"))
52
1/1
✓ Branch 8 → 9 taken 481 times.
481 return value.toLower();
53
2/2
✓ Branch 12 → 13 taken 203 times.
✓ Branch 12 → 31 taken 293 times.
496 if (name == QLatin1String("TEL")) {
54 203 QString digits;
55
4/4
✓ Branch 13 → 14 taken 203 times.
✓ Branch 14 → 15 taken 203 times.
✓ Branch 27 → 16 taken 2333 times.
✓ Branch 27 → 28 taken 203 times.
2536 for (const QChar c : value)
56
6/6
✓ Branch 17 → 18 taken 603 times.
✓ Branch 17 → 22 taken 1730 times.
✓ Branch 21 → 22 taken 201 times.
✓ Branch 21 → 23 taken 402 times.
✓ Branch 24 → 25 taken 1931 times.
✓ Branch 24 → 26 taken 402 times.
2333 if (c.isDigit() || c == QLatin1Char('+'))
57
1/1
✓ Branch 25 → 26 taken 1931 times.
1931 digits += c;
58 203 return digits;
59 203 }
60 293 return value;
61 977 }
62
63 977 QString typeKey(const ICal::Property &property) {
64 977 QStringList types;
65
2/2
✓ Branch 28 → 4 taken 813 times.
✓ Branch 28 → 29 taken 977 times.
1790 for (const ICal::Parameter &parameter : property.parameters)
66
2/2
✓ Branch 7 → 8 taken 763 times.
✓ Branch 7 → 26 taken 50 times.
813 if (parameter.name == QLatin1String("TYPE"))
67
5/5
✓ Branch 11 → 12 taken 763 times.
✓ Branch 12 → 13 taken 763 times.
✓ Branch 13 → 14 taken 763 times.
✓ Branch 23 → 15 taken 985 times.
✓ Branch 23 → 24 taken 763 times.
1748 for (const QString &type : parameter.value.split(QLatin1Char(','), Qt::SkipEmptyParts))
68
3/3
✓ Branch 16 → 17 taken 985 times.
✓ Branch 17 → 18 taken 985 times.
✓ Branch 18 → 19 taken 985 times.
1748 types << type.trimmed().toLower();
69
1/1
✓ Branch 32 → 33 taken 977 times.
977 types.removeAll(QStringLiteral("pref"));
70
1/1
✓ Branch 35 → 36 taken 977 times.
977 types.sort();
71
1/1
✓ Branch 38 → 39 taken 977 times.
1954 return types.join(QLatin1Char(','));
72 977 }
73
74 1143 QList<Unit> unitsOf(const ICal::Component &card) {
75 1143 QList<Unit> units;
76
2/2
✓ Branch 73 → 4 taken 8467 times.
✓ Branch 73 → 74 taken 1143 times.
9610 for (const ICal::Property &property : card.properties) {
77
3/3
✓ Branch 5 → 6 taken 8467 times.
✓ Branch 6 → 7 taken 111 times.
✓ Branch 6 → 8 taken 8356 times.
8467 if (isCompanion(property))
78 111 continue;
79 8356 Unit unit;
80
1/1
✓ Branch 8 → 9 taken 8356 times.
8356 unit.name = VCardCodec::baseName(property);
81
1/1
✓ Branch 11 → 12 taken 8356 times.
8356 unit.lines.append(property);
82
1/1
✓ Branch 12 → 13 taken 8356 times.
8356 unit.text = withoutGroup(property);
83
1/1
✓ Branch 15 → 16 taken 8356 times.
8356 const QString group = VCardCodec::groupOf(property);
84
2/2
✓ Branch 17 → 18 taken 111 times.
✓ Branch 17 → 44 taken 8245 times.
8356 if (!group.isEmpty())
85
2/2
✓ Branch 42 → 20 taken 1329 times.
✓ Branch 42 → 43 taken 111 times.
1440 for (const ICal::Property &companion : card.properties)
86
10/12
✓ Branch 21 → 22 taken 1329 times.
✓ Branch 22 → 23 taken 133 times.
✓ Branch 22 → 27 taken 1196 times.
✓ Branch 23 → 24 taken 133 times.
✓ Branch 25 → 26 taken 111 times.
✓ Branch 25 → 27 taken 22 times.
✓ Branch 28 → 29 taken 133 times.
✓ Branch 28 → 30 taken 1196 times.
✓ Branch 30 → 31 taken 111 times.
✓ Branch 30 → 40 taken 1218 times.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
1329 if (isCompanion(companion) && VCardCodec::groupOf(companion) == group) {
87
1/1
✓ Branch 31 → 32 taken 111 times.
111 unit.lines.append(companion);
88
3/3
✓ Branch 32 → 33 taken 111 times.
✓ Branch 35 → 36 taken 111 times.
✓ Branch 36 → 37 taken 111 times.
111 unit.text += QLatin1Char('\n') + withoutGroup(companion);
89 }
90 8356 unit.key = kMultiValued.contains(unit.name)
91
18/28
✓ Branch 45 → 46 taken 977 times.
✓ Branch 45 → 54 taken 7379 times.
✓ Branch 46 → 47 taken 977 times.
✓ Branch 48 → 49 taken 977 times.
✓ Branch 50 → 51 taken 977 times.
✓ Branch 51 → 52 taken 977 times.
✓ Branch 52 → 53 taken 977 times.
✓ Branch 53 → 55 taken 977 times.
✓ Branch 57 → 58 taken 977 times.
✓ Branch 57 → 59 taken 7379 times.
✓ Branch 59 → 60 taken 977 times.
✓ Branch 59 → 61 taken 7379 times.
✓ Branch 61 → 62 taken 977 times.
✓ Branch 61 → 63 taken 7379 times.
✓ Branch 63 → 64 taken 977 times.
✓ Branch 63 → 65 taken 7379 times.
✓ Branch 65 → 66 taken 977 times.
✓ Branch 65 → 67 taken 7379 times.
✗ Branch 91 → 92 not taken.
✗ Branch 91 → 93 not taken.
✗ Branch 95 → 96 not taken.
✗ Branch 95 → 97 not taken.
✗ Branch 99 → 100 not taken.
✗ Branch 99 → 101 not taken.
✗ Branch 104 → 105 not taken.
✗ Branch 104 → 106 not taken.
✗ Branch 109 → 110 not taken.
✗ Branch 109 → 111 not taken.
16712 ? unit.name + QChar(0x1f) + normalizedValue(unit.name, property) + QChar(0x1f) + typeKey(property)
92 8356 : unit.name;
93
1/1
✓ Branch 67 → 68 taken 8356 times.
8356 units.append(unit);
94 8356 }
95 1143 return units;
96 ✗ }
97
98 2567 QList<Unit> unitsNamed(const QList<Unit> &units, const QString &name) {
99 2567 QList<Unit> result;
100
2/2
✓ Branch 10 → 4 taken 19498 times.
✓ Branch 10 → 11 taken 2567 times.
22065 for (const Unit &unit : units)
101
2/2
✓ Branch 6 → 7 taken 2270 times.
✓ Branch 6 → 8 taken 17228 times.
19498 if (unit.name == name)
102
1/1
✓ Branch 7 → 8 taken 2270 times.
2270 result << unit;
103 2567 return result;
104 ✗ }
105
106 // Rendering of all single-valued units of `name` (several FN lines compare as one).
107 2049 QString singleText(const QList<Unit> &units, const QString &name) {
108 2049 QStringList texts;
109
2/2
✓ Branch 10 → 4 taken 15698 times.
✓ Branch 10 → 11 taken 2049 times.
17747 for (const Unit &unit : units)
110
2/2
✓ Branch 6 → 7 taken 1894 times.
✓ Branch 6 → 8 taken 13804 times.
15698 if (unit.name == name)
111
1/1
✓ Branch 7 → 8 taken 1894 times.
1894 texts << unit.text;
112
1/1
✓ Branch 12 → 13 taken 2049 times.
4098 return texts.join(QChar(0x1e));
113 2049 }
114
115 // The last unit of `key` (the one VCardMerge compared in place).
116 965 const Unit *lastWithKey(const QList<Unit> &units, const QString &key) {
117 965 const Unit *found = nullptr;
118
2/2
✓ Branch 10 → 4 taken 8953 times.
✓ Branch 10 → 11 taken 965 times.
9918 for (const Unit &unit : units)
119
2/2
✓ Branch 6 → 7 taken 633 times.
✓ Branch 6 → 8 taken 8320 times.
8953 if (unit.key == key)
120 633 found = &unit;
121 965 return found;
122 }
123
124 1143 QStringList categoriesOf(const ICal::Component &card) {
125 1143 QStringList result;
126
2/2
✓ Branch 34 → 4 taken 8467 times.
✓ Branch 34 → 35 taken 1143 times.
9610 for (const ICal::Property &property : card.properties)
127
3/3
✓ Branch 6 → 7 taken 8467 times.
✓ Branch 9 → 10 taken 156 times.
✓ Branch 9 → 32 taken 8311 times.
8467 if (VCardCodec::baseName(property) == QLatin1String("CATEGORIES"))
128
5/5
✓ Branch 10 → 11 taken 156 times.
✓ Branch 11 → 12 taken 156 times.
✓ Branch 12 → 13 taken 156 times.
✓ Branch 29 → 14 taken 224 times.
✓ Branch 29 → 30 taken 156 times.
380 for (const QString &value : ICal::splitValues(property.value))
129
3/4
✓ Branch 15 → 16 taken 224 times.
✓ Branch 16 → 17 taken 224 times.
✓ Branch 20 → 21 taken 224 times.
✗ Branch 20 → 27 not taken.
224 if (!result.contains(ICal::unescapeText(value).trimmed(), Qt::CaseInsensitive))
130
3/3
✓ Branch 21 → 22 taken 224 times.
✓ Branch 22 → 23 taken 224 times.
✓ Branch 23 → 24 taken 224 times.
380 result << ICal::unescapeText(value).trimmed();
131 1143 return result;
132 ✗ }
133
134 5 QString freeGroup(const ICal::Component &card) {
135 5 int highest = 0;
136
2/2
✓ Branch 19 → 4 taken 47 times.
✓ Branch 19 → 20 taken 5 times.
52 for (const ICal::Property &property : card.properties) {
137
2/2
✓ Branch 5 → 6 taken 47 times.
✓ Branch 6 → 7 taken 47 times.
47 const QString group = VCardCodec::groupOf(property).toUpper();
138
3/3
✓ Branch 9 → 10 taken 47 times.
✓ Branch 10 → 11 taken 10 times.
✓ Branch 10 → 16 taken 37 times.
47 if (group.startsWith(QLatin1String("ITEM")))
139
2/2
✓ Branch 11 → 12 taken 10 times.
✓ Branch 12 → 13 taken 10 times.
10 highest = qMax(highest, group.mid(4).toInt());
140 47 }
141
1/1
✓ Branch 24 → 25 taken 5 times.
10 return QStringLiteral("item%1").arg(highest + 1);
142 }
143
144 13 void removeUnit(ICal::Component *card, const Unit &unit) {
145 13 const ICal::Property &main = unit.lines.first();
146
1/1
✓ Branch 3 → 4 taken 13 times.
13 const QString group = VCardCodec::groupOf(main);
147
1/2
✓ Branch 20 → 5 taken 87 times.
✗ Branch 20 → 21 not taken.
87 for (int i = 0; i < card->properties.size(); ++i) {
148
4/4
✓ Branch 5 → 6 taken 87 times.
✓ Branch 7 → 8 taken 87 times.
✓ Branch 11 → 12 taken 74 times.
✓ Branch 11 → 13 taken 13 times.
87 if (card->properties.at(i).contentLine() != main.contentLine())
149 74 continue;
150
1/1
✓ Branch 13 → 14 taken 13 times.
13 card->properties.removeAt(i);
151
2/2
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 12 times.
13 if (!group.isEmpty())
152
1/1
✓ Branch 16 → 17 taken 1 time.
1 card->properties.removeIf([&group](const ICal::Property &p) {
153
7/10
✓ Branch 2 → 3 taken 8 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 8 taken 7 times.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 8 not taken.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 7 times.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
8 return isCompanion(p) && VCardCodec::groupOf(p) == group;
154 });
155 13 return;
156 }
157 13 }
158
159 29 void appendUnit(ICal::Component *card, const Unit &unit) {
160
1/1
✓ Branch 3 → 4 taken 29 times.
29 const QString oldGroup = VCardCodec::groupOf(unit.lines.first());
161
3/3
✓ Branch 5 → 6 taken 24 times.
✓ Branch 5 → 7 taken 5 times.
✓ Branch 7 → 8 taken 5 times.
29 const QString group = oldGroup.isEmpty() ? QString() : freeGroup(*card);
162
2/2
✓ Branch 28 → 10 taken 34 times.
✓ Branch 28 → 29 taken 29 times.
63 for (ICal::Property line : unit.lines) {
163
2/2
✓ Branch 13 → 14 taken 10 times.
✓ Branch 13 → 24 taken 24 times.
34 if (!group.isEmpty()) {
164
3/3
✓ Branch 14 → 15 taken 10 times.
✓ Branch 17 → 18 taken 10 times.
✓ Branch 18 → 19 taken 10 times.
10 line.name = group + QLatin1Char('.') + VCardCodec::baseName(line);
165 10 line.originalLine.clear();
166 }
167
1/1
✓ Branch 24 → 25 taken 34 times.
34 card->properties.append(line);
168 34 }
169 29 }
170
171 // Replaces every unit of `name` in `card` with `replacement` (at the first
172 // old position, else at the end).
173 50 void replaceSingle(ICal::Component *card, const QString &name, const QList<Unit> &replacement) {
174 50 int position = -1;
175
2/2
✓ Branch 26 → 4 taken 362 times.
✓ Branch 26 → 27 taken 50 times.
412 for (int i = card->properties.size() - 1; i >= 0; --i) {
176
8/12
✓ Branch 5 → 6 taken 362 times.
✓ Branch 7 → 8 taken 41 times.
✓ Branch 7 → 11 taken 321 times.
✓ Branch 9 → 10 taken 41 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 41 times.
✓ Branch 13 → 14 taken 362 times.
✗ Branch 13 → 15 not taken.
✓ Branch 15 → 16 taken 321 times.
✓ Branch 15 → 17 taken 41 times.
✗ Branch 49 → 50 not taken.
✗ Branch 49 → 51 not taken.
362 if (VCardCodec::baseName(card->properties.at(i)) != name || isCompanion(card->properties.at(i)))
177 321 continue;
178
1/1
✓ Branch 18 → 19 taken 41 times.
41 const QString group = VCardCodec::groupOf(card->properties.at(i));
179
1/1
✓ Branch 19 → 20 taken 41 times.
41 card->properties.removeAt(i);
180 41 position = i;
181
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 41 times.
41 if (!group.isEmpty())
182 ✗ card->properties.removeIf([&group](const ICal::Property &p) {
183 ✗ return isCompanion(p) && VCardCodec::groupOf(p) == group;
184 });
185 41 }
186
5/6
✓ Branch 27 → 28 taken 41 times.
✓ Branch 27 → 30 taken 9 times.
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 41 times.
✓ Branch 32 → 33 taken 9 times.
✓ Branch 32 → 35 taken 41 times.
50 if (position < 0 || position > card->properties.size())
187 9 position = int(card->properties.size());
188
2/2
✓ Branch 47 → 37 taken 44 times.
✓ Branch 47 → 48 taken 50 times.
94 for (const Unit &unit : replacement)
189
2/2
✓ Branch 44 → 40 taken 44 times.
✓ Branch 44 → 45 taken 44 times.
88 for (const ICal::Property &line : unit.lines)
190
1/1
✓ Branch 41 → 42 taken 44 times.
44 card->properties.insert(position++, line);
191 50 }
192
193 8 void writeCategories(ICal::Component *card, const QStringList &categories) {
194 8 int position = -1;
195
2/2
✓ Branch 13 → 4 taken 63 times.
✓ Branch 13 → 14 taken 8 times.
71 for (int i = card->properties.size() - 1; i >= 0; --i)
196
3/3
✓ Branch 6 → 7 taken 63 times.
✓ Branch 9 → 10 taken 8 times.
✓ Branch 9 → 12 taken 55 times.
63 if (VCardCodec::baseName(card->properties.at(i)) == QLatin1String("CATEGORIES")) {
197
1/1
✓ Branch 10 → 11 taken 8 times.
8 card->properties.removeAt(i);
198 8 position = i;
199 }
200
1/2
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 8 times.
8 if (categories.isEmpty())
201 ✗ return;
202 8 QStringList escaped;
203
2/2
✓ Branch 25 → 19 taken 16 times.
✓ Branch 25 → 26 taken 8 times.
24 for (const QString &category : categories)
204
2/2
✓ Branch 20 → 21 taken 16 times.
✓ Branch 21 → 22 taken 16 times.
16 escaped << ICal::escapeText(category);
205 8 ICal::Property line;
206 8 line.name = QStringLiteral("CATEGORIES");
207
1/1
✓ Branch 34 → 35 taken 8 times.
8 line.value = escaped.join(QLatin1Char(','));
208
2/3
✗ Branch 37 → 38 not taken.
✓ Branch 37 → 40 taken 8 times.
✓ Branch 41 → 42 taken 8 times.
8 card->properties.insert(position < 0 ? int(card->properties.size()) : position, line);
209 8 }
210
211 struct Cards {
212 ICal::Component base;
213 ICal::Component local;
214 ICal::Component server;
215 QString error;
216 };
217
218 396 Cards parseCards(const QString &base, const QString &local, const QString &server) {
219 396 Cards cards;
220 396 int skippedBase = 0, skippedLocal = 0, skippedServer = 0;
221
1/1
✓ Branch 2 → 3 taken 396 times.
396 if (!VCardCodec::parseTree(base, &cards.base, &cards.error, &skippedBase) ||
222
6/7
✓ Branch 3 → 4 taken 396 times.
✗ Branch 3 → 8 not taken.
✓ Branch 4 → 5 taken 396 times.
✓ Branch 5 → 6 taken 394 times.
✓ Branch 5 → 8 taken 2 times.
✓ Branch 10 → 11 taken 5 times.
✓ Branch 10 → 21 taken 391 times.
790 !VCardCodec::parseTree(local, &cards.local, &cards.error, &skippedLocal) ||
223
3/3
✓ Branch 6 → 7 taken 394 times.
✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 9 taken 391 times.
394 !VCardCodec::parseTree(server, &cards.server, &cards.error, &skippedServer)) {
224
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 20 taken 5 times.
5 if (cards.error.isEmpty())
225 ✗ cards.error = QStringLiteral("Unreadable card");
226 5 return cards;
227 }
228 // S106-152: a version whose parse dropped lines would be PUT back
229 // without them; it is a conflict the user resolves.
230
2/2
✓ Branch 21 → 22 taken 10 times.
✓ Branch 21 → 29 taken 381 times.
391 if (skippedBase + skippedLocal + skippedServer > 0)
231 10 cards.error = QStringLiteral("A version has lines MailJD cannot read; it is not merged");
232 391 return cards;
233 ✗ }
234
235 683 QString singleId(const QString &name) { return name; }
236
1/1
✓ Branch 5 → 6 taken 753 times.
1506 QString setId(const QString &key) { return QStringLiteral("SET:") + key; }
237
2/2
✓ Branch 2 → 3 taken 160 times.
✓ Branch 6 → 7 taken 160 times.
320 QString categoryId(const QString &value) { return QStringLiteral("CATEGORIES:") + value.toLower(); }
238
239
2/2
✓ Branch 2 → 3 taken 569 times.
✓ Branch 2 → 4 taken 307 times.
876 Merge::Value unitValue(const Unit *unit) { return unit ? Merge::Value{true, unit->text} : Merge::Value{}; }
240
241 234 QString plainNote(const QList<Unit> &units) {
242
1/1
✓ Branch 5 → 6 taken 234 times.
234 const QList<Unit> notes = unitsNamed(units, QStringLiteral("NOTE"));
243
3/3
✓ Branch 9 → 10 taken 68 times.
✓ Branch 9 → 11 taken 166 times.
✓ Branch 13 → 14 taken 166 times.
468 return notes.isEmpty() ? QString() : ICal::unescapeText(notes.first().lines.first().value);
244 234 }
245
246 15 Merge::Plan rawPlan(const QString &base, const QString &local, const QString &server, const QString &error) {
247 15 Merge::Plan plan;
248 15 plan.domain = QStringLiteral("vcard");
249 15 plan.comparable = false;
250 15 plan.error = error;
251 15 plan.baseText = base;
252 15 plan.localText = local;
253 15 plan.serverText = server;
254 15 Merge::Item raw;
255 15 raw.id = kRawItem;
256 15 raw.name = QStringLiteral("card");
257 15 raw.kind = Merge::Kind::Text;
258 15 raw.base = {true, base};
259 15 raw.local = {true, local};
260 15 raw.server = {true, server};
261
2/3
✓ Branch 28 → 29 taken 15 times.
✗ Branch 29 → 30 not taken.
✓ Branch 29 → 31 taken 15 times.
15 raw.state = Merge::stateOf(raw.base, raw.local, raw.server) == Merge::State::Same ? Merge::State::Same
262 : Merge::State::Conflict;
263 15 raw.basePlain = base;
264 15 raw.localPlain = local;
265 15 raw.serverPlain = server;
266
1/1
✓ Branch 35 → 36 taken 15 times.
15 raw.hunks = TextMerge::diff3(base, local, server);
267
1/1
✓ Branch 38 → 39 taken 15 times.
15 plan.items.append(raw);
268
1/1
✓ Branch 39 → 40 taken 15 times.
15 Merge::applyDefaults(&plan);
269 15 return plan;
270 60 }
271
272 // The NOTE unit of the user's text, based on the local (else server) line.
273 3 QList<Unit> noteUnits(const QString &text, const QList<Unit> &local, const QList<Unit> &server) {
274
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 3 times.
3 if (text.isEmpty())
275 ✗ return {};
276
1/1
✓ Branch 8 → 9 taken 3 times.
3 QList<Unit> source = unitsNamed(local, QStringLiteral("NOTE"));
277
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 22 taken 3 times.
3 if (source.isEmpty())
278 ✗ source = unitsNamed(server, QStringLiteral("NOTE"));
279 3 Unit unit;
280 3 unit.name = QStringLiteral("NOTE");
281 3 ICal::Property property;
282
1/2
✓ Branch 29 → 30 taken 3 times.
✗ Branch 29 → 34 not taken.
3 if (!source.isEmpty()) {
283
1/1
✓ Branch 30 → 31 taken 3 times.
3 unit.lines = source.first().lines;
284
1/1
✓ Branch 32 → 33 taken 3 times.
3 property = unit.lines.first();
285 } else {
286 ✗ property.name = QStringLiteral("NOTE");
287 ✗ unit.lines.append(property);
288 }
289
2/2
✓ Branch 41 → 42 taken 3 times.
✓ Branch 42 → 43 taken 3 times.
3 property.setValue(ICal::escapeText(text));
290
1/1
✓ Branch 44 → 45 taken 3 times.
3 unit.lines[0] = property;
291
2/4
✓ Branch 49 → 50 taken 3 times.
✓ Branch 49 → 51 taken 3 times.
✗ Branch 73 → 74 not taken.
✗ Branch 73 → 75 not taken.
6 return {unit};
292 6 }
293
294 } // namespace
295
296 250 Merge::Plan analyze(const QString &baseText, const QString &localText, const QString &serverText) {
297
1/1
✓ Branch 2 → 3 taken 250 times.
250 const Cards cards = parseCards(baseText, localText, serverText);
298
2/2
✓ Branch 4 → 5 taken 15 times.
✓ Branch 4 → 7 taken 235 times.
250 if (!cards.error.isEmpty())
299
1/1
✓ Branch 5 → 6 taken 15 times.
15 return rawPlan(baseText, localText, serverText, cards.error);
300 235 Merge::Plan plan;
301 235 plan.domain = QStringLiteral("vcard");
302 235 plan.baseText = baseText;
303 235 plan.localText = localText;
304 235 plan.serverText = serverText;
305
1/1
✓ Branch 16 → 17 taken 235 times.
235 const QList<Unit> b = unitsOf(cards.base);
306
1/1
✓ Branch 17 → 18 taken 235 times.
235 const QList<Unit> o = unitsOf(cards.local);
307
1/1
✓ Branch 18 → 19 taken 235 times.
235 const QList<Unit> t = unitsOf(cards.server);
308
309 235 QStringList singles;
310
2/2
✓ Branch 41 → 21 taken 705 times.
✓ Branch 41 → 42 taken 235 times.
940 for (const QList<Unit> *units : {&b, &o, &t})
311
2/2
✓ Branch 39 → 23 taken 5075 times.
✓ Branch 39 → 40 taken 705 times.
5780 for (const Unit &unit : *units)
312
2/2
✓ Branch 27 → 28 taken 1981 times.
✓ Branch 27 → 34 taken 2525 times.
9581 if (!kMultiValued.contains(unit.name) && !kFromTheirs.contains(unit.name) &&
313
8/8
✓ Branch 25 → 26 taken 4506 times.
✓ Branch 25 → 34 taken 569 times.
✓ Branch 30 → 31 taken 1894 times.
✓ Branch 30 → 34 taken 87 times.
✓ Branch 32 → 33 taken 683 times.
✓ Branch 32 → 34 taken 1211 times.
✓ Branch 35 → 36 taken 683 times.
✓ Branch 35 → 37 taken 4392 times.
9581 unit.name != QLatin1String("CATEGORIES") && !singles.contains(unit.name))
314
1/1
✓ Branch 36 → 37 taken 683 times.
683 singles << unit.name;
315
4/4
✓ Branch 42 → 43 taken 235 times.
✓ Branch 43 → 44 taken 235 times.
✓ Branch 146 → 45 taken 683 times.
✓ Branch 146 → 147 taken 235 times.
918 for (const QString &name : singles) {
316
1/1
✓ Branch 46 → 47 taken 683 times.
683 const QString baseValue = singleText(b, name);
317
1/1
✓ Branch 47 → 48 taken 683 times.
683 const QString localValue = singleText(o, name);
318
1/1
✓ Branch 48 → 49 taken 683 times.
683 const QString serverValue = singleText(t, name);
319 683 Merge::Item item;
320 683 item.id = singleId(name);
321 683 item.name = name;
322
6/6
✓ Branch 55 → 56 taken 448 times.
✓ Branch 55 → 59 taken 235 times.
✓ Branch 58 → 59 taken 235 times.
✓ Branch 58 → 60 taken 213 times.
✓ Branch 61 → 62 taken 470 times.
✓ Branch 61 → 69 taken 213 times.
683 if (name == QLatin1String("N") || name == QLatin1String("FN"))
323 470 item.group = QStringLiteral("name");
324
1/1
✓ Branch 69 → 70 taken 683 times.
1366 item.base = {!unitsNamed(b, name).isEmpty(), baseValue};
325
1/1
✓ Branch 75 → 76 taken 683 times.
1366 item.local = {!unitsNamed(o, name).isEmpty(), localValue};
326
1/1
✓ Branch 81 → 82 taken 683 times.
1366 item.server = {!unitsNamed(t, name).isEmpty(), serverValue};
327 // VCardMerge: local == base or local == server → the server holds the
328 // result; server == base → local; otherwise a conflict.
329
6/6
✓ Branch 88 → 89 taken 486 times.
✓ Branch 88 → 92 taken 197 times.
✓ Branch 90 → 91 taken 418 times.
✓ Branch 90 → 92 taken 68 times.
✓ Branch 93 → 94 taken 418 times.
✓ Branch 93 → 95 taken 265 times.
683 if (localValue == baseValue && serverValue == baseValue)
330 418 item.state = Merge::State::Same;
331
2/2
✓ Branch 96 → 97 taken 26 times.
✓ Branch 96 → 98 taken 239 times.
265 else if (localValue == serverValue)
332 26 item.state = Merge::State::BothSame;
333
2/2
✓ Branch 99 → 100 taken 68 times.
✓ Branch 99 → 101 taken 171 times.
239 else if (localValue == baseValue)
334 68 item.state = Merge::State::ServerOnly;
335
2/2
✓ Branch 102 → 103 taken 49 times.
✓ Branch 102 → 104 taken 122 times.
171 else if (serverValue == baseValue)
336 49 item.state = Merge::State::LocalOnly;
337 else
338 122 item.state = Merge::State::Conflict;
339
11/18
✓ Branch 107 → 108 taken 78 times.
✓ Branch 107 → 118 taken 605 times.
✓ Branch 108 → 109 taken 78 times.
✓ Branch 110 → 111 taken 78 times.
✗ Branch 110 → 118 not taken.
✓ Branch 111 → 112 taken 78 times.
✓ Branch 113 → 114 taken 78 times.
✗ Branch 113 → 118 not taken.
✓ Branch 116 → 117 taken 78 times.
✗ Branch 116 → 118 not taken.
✓ Branch 123 → 124 taken 78 times.
✓ Branch 123 → 125 taken 605 times.
✓ Branch 125 → 126 taken 78 times.
✓ Branch 125 → 139 taken 605 times.
✗ Branch 295 → 296 not taken.
✗ Branch 295 → 297 not taken.
✗ Branch 299 → 300 not taken.
✗ Branch 299 → 301 not taken.
1444 if (name == QLatin1String("NOTE") && unitsNamed(b, name).size() <= 1 && unitsNamed(o, name).size() <= 1 &&
340
5/7
✓ Branch 114 → 115 taken 78 times.
✓ Branch 119 → 120 taken 78 times.
✓ Branch 119 → 121 taken 605 times.
✓ Branch 121 → 122 taken 78 times.
✓ Branch 121 → 123 taken 605 times.
✗ Branch 291 → 292 not taken.
✗ Branch 291 → 293 not taken.
761 unitsNamed(t, name).size() <= 1) {
341 78 item.kind = Merge::Kind::Text;
342
1/1
✓ Branch 126 → 127 taken 78 times.
78 item.basePlain = plainNote(b);
343
1/1
✓ Branch 129 → 130 taken 78 times.
78 item.localPlain = plainNote(o);
344
1/1
✓ Branch 132 → 133 taken 78 times.
78 item.serverPlain = plainNote(t);
345
1/1
✓ Branch 135 → 136 taken 78 times.
78 item.hunks = TextMerge::diff3(item.basePlain, item.localPlain, item.serverPlain);
346 }
347
1/1
✓ Branch 139 → 140 taken 683 times.
683 plan.items.append(item);
348 683 }
349
350 235 QStringList keys;
351
2/2
✓ Branch 164 → 149 taken 705 times.
✓ Branch 164 → 165 taken 235 times.
940 for (const QList<Unit> *units : {&t, &o, &b})
352
2/2
✓ Branch 162 → 151 taken 5075 times.
✓ Branch 162 → 163 taken 705 times.
5780 for (const Unit &unit : *units)
353
6/6
✓ Branch 153 → 154 taken 569 times.
✓ Branch 153 → 157 taken 4506 times.
✓ Branch 155 → 156 taken 292 times.
✓ Branch 155 → 157 taken 277 times.
✓ Branch 158 → 159 taken 292 times.
✓ Branch 158 → 160 taken 4783 times.
5075 if (kMultiValued.contains(unit.name) && !keys.contains(unit.key))
354
1/1
✓ Branch 159 → 160 taken 292 times.
292 keys << unit.key;
355
4/4
✓ Branch 165 → 166 taken 235 times.
✓ Branch 166 → 167 taken 235 times.
✓ Branch 226 → 168 taken 292 times.
✓ Branch 226 → 227 taken 235 times.
527 for (const QString &key : keys) {
356
1/1
✓ Branch 169 → 170 taken 292 times.
292 const Unit *baseUnit = lastWithKey(b, key);
357
1/1
✓ Branch 170 → 171 taken 292 times.
292 const Unit *localUnit = lastWithKey(o, key);
358
1/1
✓ Branch 171 → 172 taken 292 times.
292 const Unit *serverUnit = lastWithKey(t, key);
359 292 Merge::Item item;
360
1/1
✓ Branch 172 → 173 taken 292 times.
292 item.id = setId(key);
361
4/4
✓ Branch 175 → 176 taken 92 times.
✓ Branch 175 → 180 taken 200 times.
✓ Branch 176 → 177 taken 67 times.
✓ Branch 176 → 178 taken 25 times.
292 item.name = (localUnit ? localUnit : serverUnit ? serverUnit : baseUnit)->name;
362 292 item.kind = Merge::Kind::SetValue;
363 292 item.base = unitValue(baseUnit);
364 292 item.local = unitValue(localUnit);
365 292 item.server = unitValue(serverUnit);
366 // What VCardMerge does with this key: remove it (removed locally), add
367 // or replace the local version, or keep the server's card as it is.
368
6/6
✓ Branch 191 → 192 taken 174 times.
✓ Branch 191 → 195 taken 118 times.
✓ Branch 192 → 193 taken 137 times.
✓ Branch 192 → 195 taken 37 times.
✓ Branch 193 → 194 taken 9 times.
✓ Branch 193 → 195 taken 128 times.
292 const bool removedLocally = baseUnit && serverUnit && !localUnit;
369
5/6
✓ Branch 196 → 197 taken 200 times.
✓ Branch 196 → 200 taken 92 times.
✓ Branch 197 → 198 taken 60 times.
✓ Branch 197 → 200 taken 140 times.
✓ Branch 198 → 199 taken 60 times.
✗ Branch 198 → 200 not taken.
292 const bool addedLocally = localUnit && !baseUnit && !serverUnit;
370
9/10
✓ Branch 201 → 202 taken 200 times.
✓ Branch 201 → 209 taken 92 times.
✓ Branch 202 → 203 taken 140 times.
✓ Branch 202 → 209 taken 60 times.
✓ Branch 203 → 204 taken 128 times.
✓ Branch 203 → 209 taken 12 times.
✓ Branch 205 → 206 taken 9 times.
✓ Branch 205 → 209 taken 119 times.
✓ Branch 207 → 208 taken 9 times.
✗ Branch 207 → 209 not taken.
301 const bool changedLocally = localUnit && baseUnit && serverUnit && localUnit->text != baseUnit->text &&
371 9 serverUnit->text == baseUnit->text;
372 358 const auto sameValue = [](const Merge::Value &x, const Merge::Value &y) {
373
5/6
✓ Branch 2 → 3 taken 263 times.
✓ Branch 2 → 7 taken 95 times.
✓ Branch 3 → 4 taken 238 times.
✓ Branch 3 → 6 taken 25 times.
✓ Branch 5 → 6 taken 238 times.
✗ Branch 5 → 7 not taken.
358 return x.present == y.present && (!x.present || x.text == y.text);
374 };
375
6/6
✓ Branch 210 → 211 taken 283 times.
✓ Branch 210 → 213 taken 9 times.
✓ Branch 211 → 212 taken 223 times.
✓ Branch 211 → 213 taken 60 times.
✓ Branch 212 → 213 taken 9 times.
✓ Branch 212 → 214 taken 214 times.
292 if (removedLocally || addedLocally || changedLocally)
376 78 item.state = Merge::State::LocalOnly;
377
2/2
✓ Branch 215 → 216 taken 144 times.
✓ Branch 215 → 221 taken 70 times.
214 else if (sameValue(item.local, item.server))
378
2/2
✓ Branch 217 → 218 taken 119 times.
✓ Branch 217 → 219 taken 25 times.
144 item.state = sameValue(item.local, item.base) ? Merge::State::Same : Merge::State::BothSame;
379 else
380 70 item.state = Merge::State::ServerOnly;
381
1/1
✓ Branch 222 → 223 taken 292 times.
292 plan.items.append(item);
382 292 }
383
384
1/1
✓ Branch 227 → 228 taken 235 times.
235 const QStringList baseCategories = categoriesOf(cards.base);
385
1/1
✓ Branch 228 → 229 taken 235 times.
235 const QStringList localCategories = categoriesOf(cards.local);
386
1/1
✓ Branch 229 → 230 taken 235 times.
235 const QStringList serverCategories = categoriesOf(cards.server);
387 235 QStringList seen;
388
2/2
✓ Branch 267 → 232 taken 705 times.
✓ Branch 267 → 268 taken 235 times.
940 for (const QStringList *list : {&serverCategories, &localCategories, &baseCategories})
389
2/2
✓ Branch 265 → 234 taken 123 times.
✓ Branch 265 → 266 taken 705 times.
828 for (const QString &value : *list) {
390
2/2
✓ Branch 236 → 237 taken 64 times.
✓ Branch 236 → 238 taken 59 times.
123 if (seen.contains(value, Qt::CaseInsensitive))
391 64 continue;
392
1/1
✓ Branch 238 → 239 taken 59 times.
59 seen << value;
393 177 const auto valueIn = [&value](const QStringList &values) {
394 177 return Merge::Value{values.contains(value, Qt::CaseInsensitive), value};
395 59 };
396 59 Merge::Item item;
397
1/1
✓ Branch 239 → 240 taken 59 times.
59 item.id = categoryId(value);
398 59 item.name = QStringLiteral("CATEGORIES");
399 59 item.kind = Merge::Kind::SetValue;
400 59 item.base = valueIn(baseCategories);
401 59 item.local = valueIn(localCategories);
402 59 item.server = valueIn(serverCategories);
403
1/1
✓ Branch 257 → 258 taken 59 times.
59 item.state = Merge::stateOf(item.base, item.local, item.server);
404
1/2
✗ Branch 258 → 259 not taken.
✓ Branch 258 → 260 taken 59 times.
59 if (item.state == Merge::State::Conflict) // presence only: cannot differ on both
405 ✗ item.state = Merge::State::LocalOnly;
406
1/1
✓ Branch 260 → 261 taken 59 times.
59 plan.items.append(item);
407 59 }
408
1/1
✓ Branch 268 → 269 taken 235 times.
235 Merge::applyDefaults(&plan);
409 235 return plan;
410 2299 }
411
412 156 Applied apply(const Merge::Plan &plan, const QString &now) {
413 156 Applied applied;
414
2/2
✓ Branch 16 → 4 taken 685 times.
✓ Branch 16 → 17 taken 150 times.
835 for (const Merge::Item &item : plan.items)
415
3/3
✓ Branch 5 → 6 taken 685 times.
✓ Branch 6 → 7 taken 6 times.
✓ Branch 6 → 14 taken 679 times.
685 if (Merge::itemOpen(plan, item)) {
416 6 applied.error = QStringLiteral("Open conflicts remain");
417 6 return applied;
418 }
419
2/2
✓ Branch 17 → 18 taken 4 times.
✓ Branch 17 → 26 taken 146 times.
150 if (!plan.comparable) {
420
1/1
✓ Branch 18 → 19 taken 4 times.
4 const Merge::Item *raw = plan.item(kRawItem);
421
1/2
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 22 taken 4 times.
4 if (!raw) {
422 ✗ applied.error = plan.error;
423 ✗ return applied;
424 }
425 4 applied.ok = true;
426
1/1
✓ Branch 22 → 23 taken 4 times.
4 applied.text = Merge::resultText(*raw);
427 4 return applied;
428 }
429
1/1
✓ Branch 26 → 27 taken 146 times.
146 const Cards cards = parseCards(plan.baseText, plan.localText, plan.serverText);
430
1/2
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 31 taken 146 times.
146 if (!cards.error.isEmpty()) {
431 ✗ applied.error = cards.error;
432 ✗ return applied;
433 }
434
1/1
✓ Branch 31 → 32 taken 146 times.
146 const QList<Unit> b = unitsOf(cards.base);
435
1/1
✓ Branch 32 → 33 taken 146 times.
146 const QList<Unit> o = unitsOf(cards.local);
436
1/1
✓ Branch 33 → 34 taken 146 times.
146 const QList<Unit> t = unitsOf(cards.server);
437 146 ICal::Component merged = cards.server;
438
439
2/2
✓ Branch 64 → 37 taken 669 times.
✓ Branch 64 → 65 taken 146 times.
815 for (const Merge::Item &item : plan.items) {
440
2/2
✓ Branch 38 → 39 taken 254 times.
✓ Branch 38 → 40 taken 415 times.
669 if (item.kind == Merge::Kind::SetValue)
441 254 continue;
442
3/4
✓ Branch 40 → 41 taken 255 times.
✗ Branch 40 → 48 not taken.
✓ Branch 40 → 55 taken 3 times.
✓ Branch 40 → 61 taken 157 times.
415 switch (item.choice) {
443 255 case Merge::Choice::Local:
444
2/2
✓ Branch 42 → 43 taken 47 times.
✓ Branch 42 → 47 taken 208 times.
255 if (item.local.text != item.server.text)
445
2/2
✓ Branch 43 → 44 taken 47 times.
✓ Branch 44 → 45 taken 47 times.
47 replaceSingle(&merged, item.name, unitsNamed(o, item.name));
446 255 break;
447 ✗ case Merge::Choice::Base:
448 ✗ if (item.base.text != item.server.text)
449 ✗ replaceSingle(&merged, item.name, unitsNamed(b, item.name));
450 ✗ break;
451 3 case Merge::Choice::Edited:
452
3/3
✓ Branch 55 → 56 taken 3 times.
✓ Branch 56 → 57 taken 3 times.
✓ Branch 57 → 58 taken 3 times.
3 replaceSingle(&merged, item.name, noteUnits(Merge::resultText(item), o, t));
453 3 break;
454 157 default:
455 157 break;
456 }
457 }
458
459 // The chosen presence of a set key: the side's value or none.
460
2/2
✓ Branch 2 → 3 taken 461 times.
✓ Branch 3 → 4 taken 461 times.
461 const auto chosen = [&plan](const QString &key) -> const Merge::Item * { return plan.item(setId(key)); };
461 145 const auto wantsAbsent = [](const Merge::Item *item) {
462
4/4
✓ Branch 2 → 3 taken 73 times.
✓ Branch 2 → 4 taken 72 times.
✓ Branch 3 → 4 taken 64 times.
✓ Branch 3 → 6 taken 9 times.
281 return (item->choice == Merge::Choice::Local && !item->local.present) ||
463
1/4
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 7 taken 136 times.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 7 not taken.
281 (item->choice == Merge::Choice::Base && !item->base.present);
464 };
465
2/2
✓ Branch 80 → 67 taken 1124 times.
✓ Branch 80 → 81 taken 146 times.
1270 for (const Unit &unit : t)
466
2/2
✓ Branch 69 → 70 taken 145 times.
✓ Branch 69 → 78 taken 979 times.
1124 if (kMultiValued.contains(unit.name))
467
6/7
✓ Branch 70 → 71 taken 145 times.
✓ Branch 71 → 72 taken 145 times.
✗ Branch 71 → 75 not taken.
✓ Branch 73 → 74 taken 9 times.
✓ Branch 73 → 75 taken 136 times.
✓ Branch 76 → 77 taken 9 times.
✓ Branch 76 → 78 taken 136 times.
145 if (const Merge::Item *item = chosen(unit.key); item && wantsAbsent(item))
468
1/1
✓ Branch 77 → 78 taken 9 times.
9 removeUnit(&merged, unit);
469 292 QSet<QString> serverKeys;
470
2/2
✓ Branch 88 → 84 taken 1124 times.
✓ Branch 88 → 89 taken 146 times.
1270 for (const Unit &unit : t)
471
1/1
✓ Branch 85 → 86 taken 1124 times.
1124 serverKeys.insert(unit.key);
472
2/2
✓ Branch 105 → 91 taken 1104 times.
✓ Branch 105 → 106 taken 146 times.
1250 for (const Unit &unit : o)
473
6/6
✓ Branch 93 → 94 taken 146 times.
✓ Branch 93 → 97 taken 958 times.
✓ Branch 95 → 96 taken 53 times.
✓ Branch 95 → 97 taken 93 times.
✓ Branch 98 → 99 taken 53 times.
✓ Branch 98 → 103 taken 1051 times.
1104 if (kMultiValued.contains(unit.name) && !serverKeys.contains(unit.key))
474
4/5
✓ Branch 99 → 100 taken 53 times.
✓ Branch 100 → 101 taken 53 times.
✗ Branch 100 → 103 not taken.
✓ Branch 101 → 102 taken 25 times.
✓ Branch 101 → 103 taken 28 times.
53 if (const Merge::Item *item = chosen(unit.key); item && item->choice == Merge::Choice::Local)
475
1/1
✓ Branch 102 → 103 taken 25 times.
25 appendUnit(&merged, unit);
476 // A local value whose server version differs replaces it (Sprint 103
477 // S103.1.7: a pinned ADR;GEO=, a changed relation label).
478
2/2
✓ Branch 128 → 108 taken 1104 times.
✓ Branch 128 → 129 taken 146 times.
1250 for (const Unit &unit : o) {
479
3/3
✓ Branch 110 → 111 taken 146 times.
✓ Branch 110 → 113 taken 958 times.
✓ Branch 111 → 112 taken 146 times.
1104 const Merge::Item *item = kMultiValued.contains(unit.name) ? chosen(unit.key) : nullptr;
480
4/4
✓ Branch 114 → 115 taken 146 times.
✓ Branch 114 → 116 taken 958 times.
✓ Branch 115 → 116 taken 57 times.
✓ Branch 115 → 117 taken 89 times.
1104 if (!item || item->choice != Merge::Choice::Local)
481 1015 continue;
482
1/1
✓ Branch 117 → 118 taken 89 times.
89 const Unit *serverUnit = lastWithKey(t, unit.key);
483
6/6
✓ Branch 118 → 119 taken 64 times.
✓ Branch 118 → 122 taken 25 times.
✓ Branch 120 → 121 taken 4 times.
✓ Branch 120 → 122 taken 60 times.
✓ Branch 123 → 124 taken 4 times.
✓ Branch 123 → 126 taken 85 times.
89 if (serverUnit && serverUnit->text != unit.text) {
484
1/1
✓ Branch 124 → 125 taken 4 times.
4 removeUnit(&merged, *serverUnit);
485
1/1
✓ Branch 125 → 126 taken 4 times.
4 appendUnit(&merged, unit);
486 }
487 }
488
2/2
✓ Branch 158 → 131 taken 1053 times.
✓ Branch 158 → 159 taken 146 times.
1199 for (const Unit &unit : b) {
489
3/3
✓ Branch 133 → 134 taken 117 times.
✓ Branch 133 → 136 taken 936 times.
✓ Branch 134 → 135 taken 117 times.
1053 const Merge::Item *item = kMultiValued.contains(unit.name) ? chosen(unit.key) : nullptr;
490
4/9
✓ Branch 137 → 138 taken 117 times.
✓ Branch 137 → 141 taken 936 times.
✗ Branch 138 → 139 not taken.
✓ Branch 138 → 141 taken 117 times.
✗ Branch 139 → 140 not taken.
✗ Branch 140 → 141 not taken.
✗ Branch 140 → 142 not taken.
✓ Branch 143 → 144 taken 1053 times.
✗ Branch 143 → 145 not taken.
1053 if (!item || item->choice != Merge::Choice::Base || lastWithKey(b, unit.key) != &unit)
491 1053 continue;
492 ✗ const Unit *serverUnit = lastWithKey(t, unit.key);
493 ✗ if (serverUnit && serverUnit->text == unit.text)
494 ✗ continue;
495 ✗ if (serverUnit)
496 ✗ removeUnit(&merged, *serverUnit);
497 ✗ appendUnit(&merged, unit);
498 }
499
500
1/1
✓ Branch 159 → 160 taken 146 times.
292 const QStringList baseCategories = categoriesOf(cards.base);
501
1/1
✓ Branch 160 → 161 taken 146 times.
292 const QStringList localCategories = categoriesOf(cards.local);
502
1/1
✓ Branch 161 → 162 taken 146 times.
292 const QStringList serverCategories = categoriesOf(cards.server);
503 101 const auto included = [&](const QString &value) {
504
2/2
✓ Branch 2 → 3 taken 101 times.
✓ Branch 3 → 4 taken 101 times.
101 const Merge::Item *item = plan.item(categoryId(value));
505
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 101 times.
101 if (!item)
506 ✗ return false;
507
2/2
✓ Branch 7 → 8 taken 56 times.
✓ Branch 7 → 12 taken 45 times.
157 const QStringList &side = item->choice == Merge::Choice::Server ? serverCategories
508
1/2
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 56 times.
56 : item->choice == Merge::Choice::Base ? baseCategories
509 : localCategories;
510 101 return side.contains(value, Qt::CaseInsensitive);
511 146 };
512 ✗ QStringList categories;
513
2/2
✓ Branch 179 → 164 taken 438 times.
✓ Branch 179 → 180 taken 146 times.
584 for (const QStringList *list : {&serverCategories, &localCategories, &baseCategories})
514
2/2
✓ Branch 177 → 166 taken 101 times.
✓ Branch 177 → 178 taken 438 times.
539 for (const QString &value : *list)
515
7/7
✓ Branch 167 → 168 taken 101 times.
✓ Branch 168 → 169 taken 89 times.
✓ Branch 168 → 172 taken 12 times.
✓ Branch 170 → 171 taken 39 times.
✓ Branch 170 → 172 taken 50 times.
✓ Branch 173 → 174 taken 39 times.
✓ Branch 173 → 175 taken 62 times.
101 if (included(value) && !categories.contains(value, Qt::CaseInsensitive))
516
1/1
✓ Branch 174 → 175 taken 39 times.
39 categories << value;
517
3/3
✓ Branch 180 → 181 taken 146 times.
✓ Branch 181 → 182 taken 8 times.
✓ Branch 181 → 183 taken 138 times.
146 if (categories != serverCategories)
518
1/1
✓ Branch 182 → 183 taken 8 times.
8 writeCategories(&merged, categories);
519
520
4/4
✓ Branch 183 → 184 taken 146 times.
✓ Branch 184 → 185 taken 146 times.
✓ Branch 188 → 189 taken 89 times.
✓ Branch 188 → 211 taken 57 times.
146 if (ICal::serialize(merged) != ICal::serialize(cards.server))
521
1/1
✓ Branch 200 → 201 taken 89 times.
89 merged.setPropertyValue(QStringLiteral("REV"),
522
10/16
✓ Branch 190 → 191 taken 81 times.
✓ Branch 190 → 196 taken 8 times.
✓ Branch 191 → 192 taken 81 times.
✓ Branch 195 → 197 taken 81 times.
✓ Branch 204 → 205 taken 81 times.
✓ Branch 204 → 206 taken 8 times.
✓ Branch 206 → 207 taken 81 times.
✓ Branch 206 → 208 taken 8 times.
✓ Branch 208 → 209 taken 81 times.
✓ Branch 208 → 210 taken 8 times.
✗ Branch 261 → 262 not taken.
✗ Branch 261 → 263 not taken.
✗ Branch 265 → 266 not taken.
✗ Branch 265 → 267 not taken.
✗ Branch 269 → 270 not taken.
✗ Branch 269 → 271 not taken.
259 now.isEmpty() ? QDateTime::currentDateTimeUtc().toString(QStringLiteral("yyyyMMdd'T'HHmmss'Z'"))
523 : now);
524 146 applied.ok = true;
525
1/1
✓ Branch 211 → 212 taken 146 times.
146 applied.text = ICal::serialize(merged);
526 146 return applied;
527 146 }
528
529 } // namespace VCardMergePlan
530