data/TextMerge3.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "TextMerge3.h" | ||
| 2 | |||
| 3 | #include "LineDiff.h" | ||
| 4 | |||
| 5 | #include <QRegularExpression> | ||
| 6 | |||
| 7 | #include <algorithm> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | namespace TextMerge { | ||
| 11 | |||
| 12 | namespace { | ||
| 13 | |||
| 14 | constexpr int kWordLimit = 400; | ||
| 15 | |||
| 16 | 1117 | HunkKind classify(const QStringList &base, const QStringList &local, const QStringList &server) { | |
| 17 |
4/6✓ Branch 3 → 4 taken 461 times.
✓ Branch 3 → 7 taken 656 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 461 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 1117 times.
|
1117 | if (local == base && server == base) |
| 18 | ✗ | return HunkKind::Same; | |
| 19 |
2/2✓ Branch 11 → 12 taken 461 times.
✓ Branch 11 → 13 taken 656 times.
|
1117 | if (local == base) |
| 20 | 461 | return HunkKind::ServerOnly; | |
| 21 |
2/2✓ Branch 14 → 15 taken 427 times.
✓ Branch 14 → 16 taken 229 times.
|
656 | if (server == base) |
| 22 | 427 | return HunkKind::LocalOnly; | |
| 23 |
2/2✓ Branch 17 → 18 taken 15 times.
✓ Branch 17 → 19 taken 214 times.
|
229 | if (local == server) |
| 24 | 15 | return HunkKind::BothSame; | |
| 25 | 214 | return HunkKind::Conflict; | |
| 26 | } | ||
| 27 | |||
| 28 | 76 | QStringList words(const QString &line) { | |
| 29 | 6 | static const QRegularExpression token(QStringLiteral("\\w+|\\s+|[^\\w\\s]"), | |
| 30 |
4/7✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 15 taken 73 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 3 times.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
|
82 | QRegularExpression::UseUnicodePropertiesOption); |
| 31 | 76 | QStringList result; | |
| 32 |
1/1✓ Branch 16 → 17 taken 76 times.
|
76 | auto it = token.globalMatch(line); |
| 33 |
3/3✓ Branch 24 → 25 taken 395 times.
✓ Branch 25 → 18 taken 319 times.
✓ Branch 25 → 26 taken 76 times.
|
395 | while (it.hasNext()) |
| 34 |
3/3✓ Branch 18 → 19 taken 319 times.
✓ Branch 19 → 20 taken 319 times.
✓ Branch 20 → 21 taken 319 times.
|
319 | result.append(it.next().captured()); |
| 35 | 76 | return result; | |
| 36 | 76 | } | |
| 37 | |||
| 38 | } // namespace | ||
| 39 | |||
| 40 | 10 | int Result::conflicts() const { | |
| 41 | 10 | return int(std::count_if(hunks.cbegin(), hunks.cend(), | |
| 42 | 39 | [](const Hunk &hunk) { return hunk.kind == HunkKind::Conflict; })); | |
| 43 | } | ||
| 44 | |||
| 45 | 1 | int Result::changes() const { | |
| 46 | 1 | return int(std::count_if(hunks.cbegin(), hunks.cend(), | |
| 47 | 5 | [](const Hunk &hunk) { return hunk.kind != HunkKind::Same; })); | |
| 48 | } | ||
| 49 | |||
| 50 |
1/1✓ Branch 5 → 6 taken 4735 times.
|
4735 | QStringList splitText(const QString &text) { return text.split(QLatin1Char('\n')); } |
| 51 | |||
| 52 |
1/1✓ Branch 4 → 5 taken 2142 times.
|
2142 | QString joinText(const QStringList &lines) { return lines.join(QLatin1Char('\n')); } |
| 53 | |||
| 54 | 745 | Result diff3(const QString &baseText, const QString &localText, const QString &serverText) { | |
| 55 |
1/1✓ Branch 2 → 3 taken 745 times.
|
745 | const QStringList base = splitText(baseText); |
| 56 |
1/1✓ Branch 3 → 4 taken 745 times.
|
745 | const QStringList local = splitText(localText); |
| 57 |
1/1✓ Branch 4 → 5 taken 745 times.
|
745 | const QStringList server = splitText(serverText); |
| 58 |
1/1✓ Branch 5 → 6 taken 745 times.
|
745 | const QList<int> toLocal = LineDiff::match(base, local); |
| 59 |
1/1✓ Branch 6 → 7 taken 745 times.
|
745 | const QList<int> toServer = LineDiff::match(base, server); |
| 60 | 745 | Result result; | |
| 61 | 745 | int i = 0, j = 0, k = 0; | |
| 62 | 745 | const int nb = int(base.size()), nl = int(local.size()), ns = int(server.size()); | |
| 63 |
6/6✓ Branch 79 → 11 taken 2216 times.
✓ Branch 79 → 80 taken 826 times.
✓ Branch 80 → 11 taken 28 times.
✓ Branch 80 → 81 taken 798 times.
✓ Branch 81 → 11 taken 53 times.
✓ Branch 81 → 82 taken 745 times.
|
3042 | while (i < nb || j < nl || k < ns) { |
| 64 |
8/8✓ Branch 11 → 12 taken 2216 times.
✓ Branch 11 → 17 taken 81 times.
✓ Branch 13 → 14 taken 1627 times.
✓ Branch 13 → 17 taken 589 times.
✓ Branch 15 → 16 taken 1180 times.
✓ Branch 15 → 17 taken 447 times.
✓ Branch 18 → 19 taken 1180 times.
✓ Branch 18 → 39 taken 1117 times.
|
2297 | if (i < nb && toLocal.at(i) == j && toServer.at(i) == k) { |
| 65 | 1180 | Hunk same; | |
| 66 |
8/8✓ Branch 27 → 28 taken 23775 times.
✓ Branch 27 → 33 taken 466 times.
✓ Branch 29 → 30 taken 23387 times.
✓ Branch 29 → 33 taken 388 times.
✓ Branch 31 → 32 taken 23061 times.
✓ Branch 31 → 33 taken 326 times.
✓ Branch 34 → 20 taken 23061 times.
✓ Branch 34 → 35 taken 1180 times.
|
24241 | while (i < nb && toLocal.at(i) == j && toServer.at(i) == k) { |
| 67 |
1/1✓ Branch 21 → 22 taken 23061 times.
|
23061 | same.base.append(base.at(i)); |
| 68 |
1/1✓ Branch 23 → 24 taken 23061 times.
|
23061 | same.local.append(local.at(j)); |
| 69 |
1/1✓ Branch 25 → 26 taken 23061 times.
|
23061 | same.server.append(server.at(k)); |
| 70 | 23061 | ++i, ++j, ++k; | |
| 71 | } | ||
| 72 |
1/1✓ Branch 35 → 36 taken 1180 times.
|
1180 | result.hunks.append(same); |
| 73 | 1180 | continue; | |
| 74 | 1180 | } | |
| 75 | // The next base line matched on both sides ends the changed run. | ||
| 76 | 1117 | int sync = i; | |
| 77 |
8/8✓ Branch 41 → 42 taken 4267 times.
✓ Branch 41 → 47 taken 360 times.
✓ Branch 43 → 44 taken 1190 times.
✓ Branch 43 → 46 taken 3077 times.
✓ Branch 45 → 46 taken 433 times.
✓ Branch 45 → 47 taken 757 times.
✓ Branch 48 → 40 taken 3510 times.
✓ Branch 48 → 49 taken 1117 times.
|
4627 | while (sync < nb && (toLocal.at(sync) < 0 || toServer.at(sync) < 0)) |
| 78 | 3510 | ++sync; | |
| 79 |
2/2✓ Branch 49 → 50 taken 757 times.
✓ Branch 49 → 52 taken 360 times.
|
1117 | const int localEnd = sync < nb ? toLocal.at(sync) : nl; |
| 80 |
2/2✓ Branch 53 → 54 taken 757 times.
✓ Branch 53 → 56 taken 360 times.
|
1117 | const int serverEnd = sync < nb ? toServer.at(sync) : ns; |
| 81 | 1117 | Hunk changed; | |
| 82 |
1/1✓ Branch 57 → 58 taken 1117 times.
|
1117 | changed.base = base.mid(i, sync - i); |
| 83 |
1/1✓ Branch 60 → 61 taken 1117 times.
|
1117 | changed.local = local.mid(j, localEnd - j); |
| 84 |
1/1✓ Branch 63 → 64 taken 1117 times.
|
1117 | changed.server = server.mid(k, serverEnd - k); |
| 85 |
1/1✓ Branch 66 → 67 taken 1117 times.
|
1117 | changed.kind = classify(changed.base, changed.local, changed.server); |
| 86 |
6/8✓ Branch 68 → 69 taken 355 times.
✓ Branch 68 → 73 taken 762 times.
✓ Branch 70 → 71 taken 204 times.
✓ Branch 70 → 73 taken 151 times.
✓ Branch 72 → 73 taken 204 times.
✗ Branch 72 → 74 not taken.
✓ Branch 75 → 76 taken 1117 times.
✗ Branch 75 → 77 not taken.
|
1117 | if (!changed.base.isEmpty() || !changed.local.isEmpty() || !changed.server.isEmpty()) |
| 87 |
1/1✓ Branch 76 → 77 taken 1117 times.
|
1117 | result.hunks.append(changed); |
| 88 | 1117 | i = sync; | |
| 89 | 1117 | j = localEnd; | |
| 90 | 1117 | k = serverEnd; | |
| 91 | 1117 | } | |
| 92 | 745 | return result; | |
| 93 | 745 | } | |
| 94 | |||
| 95 | 806 | QList<Pick> defaultPicks(const Result &result) { | |
| 96 | 806 | QList<Pick> picks; | |
| 97 |
2/2✓ Branch 14 → 4 taken 2484 times.
✓ Branch 14 → 15 taken 806 times.
|
3290 | for (const Hunk &hunk : result.hunks) { |
| 98 |
3/3✓ Branch 5 → 6 taken 514 times.
✓ Branch 5 → 8 taken 215 times.
✓ Branch 5 → 10 taken 1755 times.
|
2484 | switch (hunk.kind) { |
| 99 | 514 | case HunkKind::ServerOnly: | |
| 100 |
1/1✓ Branch 6 → 7 taken 514 times.
|
514 | picks.append(Pick::Server); |
| 101 | 514 | break; | |
| 102 | 215 | case HunkKind::Conflict: | |
| 103 |
1/1✓ Branch 8 → 9 taken 215 times.
|
215 | picks.append(Pick::Open); |
| 104 | 215 | break; | |
| 105 | 1755 | default: | |
| 106 |
1/1✓ Branch 10 → 11 taken 1755 times.
|
1755 | picks.append(Pick::Local); |
| 107 | 1755 | break; | |
| 108 | } | ||
| 109 | } | ||
| 110 | 806 | return picks; | |
| 111 | ✗ | } | |
| 112 | |||
| 113 | 7236 | QStringList hunkLines(const Hunk &hunk, Pick pick) { | |
| 114 |
4/5✓ Branch 2 → 3 taken 1269 times.
✓ Branch 2 → 4 taken 2 times.
✓ Branch 2 → 5 taken 809 times.
✓ Branch 2 → 6 taken 5156 times.
✗ Branch 2 → 7 not taken.
|
7236 | switch (pick) { |
| 115 | 1269 | case Pick::Server: | |
| 116 | 1269 | return hunk.server; | |
| 117 | 2 | case Pick::Both: | |
| 118 | 2 | return hunk.local + hunk.server; | |
| 119 | 809 | case Pick::Base: | |
| 120 | 809 | return hunk.base; | |
| 121 | 5156 | case Pick::Local: | |
| 122 | case Pick::Open: | ||
| 123 | 5156 | break; | |
| 124 | } | ||
| 125 | 5156 | return hunk.local; | |
| 126 | } | ||
| 127 | |||
| 128 | 2142 | QString apply(const Result &result, const QList<Pick> &picks, bool *open) { | |
| 129 |
2/2✓ Branch 2 → 3 taken 96 times.
✓ Branch 2 → 4 taken 2046 times.
|
2142 | if (open) |
| 130 | 96 | *open = false; | |
| 131 | 2142 | QStringList lines; | |
| 132 |
2/2✓ Branch 23 → 5 taken 7236 times.
✓ Branch 23 → 24 taken 2142 times.
|
9378 | for (int index = 0; index < result.hunks.size(); ++index) { |
| 133 | 7236 | const Hunk &hunk = result.hunks.at(index); | |
| 134 |
1/2✓ Branch 7 → 8 taken 7236 times.
✗ Branch 7 → 10 not taken.
|
7236 | const Pick pick = index < picks.size() ? picks.at(index) : Pick::Open; |
| 135 |
5/6✓ Branch 11 → 12 taken 162 times.
✓ Branch 11 → 15 taken 7074 times.
✓ Branch 12 → 13 taken 162 times.
✗ Branch 12 → 15 not taken.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 161 times.
|
7236 | if (pick == Pick::Open && hunk.kind == HunkKind::Conflict && open) |
| 136 | 1 | *open = true; | |
| 137 |
4/4✓ Branch 15 → 16 taken 3505 times.
✓ Branch 15 → 17 taken 3731 times.
✓ Branch 18 → 19 taken 7236 times.
✓ Branch 19 → 20 taken 7236 times.
|
7236 | lines += hunkLines(hunk, hunk.kind == HunkKind::Same ? Pick::Local : pick); |
| 138 | } | ||
| 139 |
1/1✓ Branch 24 → 25 taken 2142 times.
|
4284 | return joinText(lines); |
| 140 | 2142 | } | |
| 141 | |||
| 142 | 38 | QList<Segment> wordSegments(const QString &from, const QString &to) { | |
| 143 |
1/1✓ Branch 2 → 3 taken 38 times.
|
38 | const QStringList a = words(from); |
| 144 |
1/1✓ Branch 3 → 4 taken 38 times.
|
38 | const QStringList b = words(to); |
| 145 |
2/2✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 37 times.
|
38 | if (to.isEmpty()) |
| 146 | 1 | return {}; | |
| 147 |
3/6✓ Branch 8 → 9 taken 37 times.
✗ Branch 8 → 11 not taken.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 37 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 21 taken 37 times.
|
37 | if (a.size() > kWordLimit || b.size() > kWordLimit) |
| 148 | ✗ | return {{to, from != to}}; | |
| 149 | 37 | const int n = int(a.size()), m = int(b.size()); | |
| 150 |
2/2✓ Branch 27 → 28 taken 37 times.
✓ Branch 28 → 29 taken 37 times.
|
37 | std::vector<std::vector<int>> table(size_t(n) + 1, std::vector<int>(size_t(m) + 1, 0)); |
| 151 |
2/2✓ Branch 52 → 33 taken 157 times.
✓ Branch 52 → 53 taken 37 times.
|
194 | for (int i = n - 1; i >= 0; --i) |
| 152 |
2/2✓ Branch 50 → 34 taken 769 times.
✓ Branch 50 → 51 taken 157 times.
|
926 | for (int j = m - 1; j >= 0; --j) |
| 153 |
2/2✓ Branch 37 → 38 taken 175 times.
✓ Branch 37 → 41 taken 594 times.
|
1363 | table[size_t(i)][size_t(j)] = a.at(i) == b.at(j) ? table[size_t(i) + 1][size_t(j) + 1] + 1 |
| 154 | 594 | : std::max(table[size_t(i) + 1][size_t(j)], table[size_t(i)][size_t(j) + 1]); | |
| 155 | 37 | QList<Segment> segments; | |
| 156 | 161 | const auto add = [&segments](const QString &text, bool changed) { | |
| 157 |
6/6✓ Branch 3 → 4 taken 124 times.
✓ Branch 3 → 7 taken 37 times.
✓ Branch 5 → 6 taken 63 times.
✓ Branch 5 → 7 taken 61 times.
✓ Branch 8 → 9 taken 63 times.
✓ Branch 8 → 11 taken 98 times.
|
161 | if (!segments.isEmpty() && segments.last().changed == changed) |
| 158 | 63 | segments.last().text += text; | |
| 159 | else | ||
| 160 | 98 | segments.append({text, changed}); | |
| 161 |
1/3✓ Branch 12 → 13 taken 98 times.
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 20 not taken.
|
259 | }; |
| 162 | 37 | int i = 0, j = 0; | |
| 163 |
2/2✓ Branch 78 → 54 taken 203 times.
✓ Branch 78 → 79 taken 37 times.
|
240 | while (j < m) { |
| 164 |
6/6✓ Branch 54 → 55 taken 183 times.
✓ Branch 54 → 60 taken 20 times.
✓ Branch 58 → 59 taken 115 times.
✓ Branch 58 → 60 taken 68 times.
✓ Branch 61 → 62 taken 115 times.
✓ Branch 61 → 65 taken 88 times.
|
203 | if (i < n && a.at(i) == b.at(j)) { |
| 165 |
1/1✓ Branch 63 → 64 taken 115 times.
|
115 | add(b.at(j), false); |
| 166 | 115 | ++i, ++j; | |
| 167 |
6/6✓ Branch 65 → 66 taken 68 times.
✓ Branch 65 → 72 taken 20 times.
✓ Branch 70 → 71 taken 42 times.
✓ Branch 70 → 72 taken 26 times.
✓ Branch 73 → 74 taken 42 times.
✓ Branch 73 → 75 taken 46 times.
|
88 | } else if (i < n && table[size_t(i) + 1][size_t(j)] >= table[size_t(i)][size_t(j) + 1]) { |
| 168 | 42 | ++i; | |
| 169 | } else { | ||
| 170 |
1/1✓ Branch 76 → 77 taken 46 times.
|
46 | add(b.at(j), true); |
| 171 | 46 | ++j; | |
| 172 | } | ||
| 173 | } | ||
| 174 | 37 | return segments; | |
| 175 | 38 | } | |
| 176 | |||
| 177 | } // namespace TextMerge | ||
| 178 |