1 | #include "GitCache.h" |
2 | |
3 | #include <QLogger.h> |
4 | #include <WipRevisionInfo.h> |
5 | |
6 | using namespace QLogger; |
7 | |
8 | GitCache::GitCache(QObject *parent) |
9 | : QObject(parent) |
10 | , mCommitsMutex(QMutex::Recursive) |
11 | , mRevisionsMutex(QMutex::Recursive) |
12 | , mReferencesMutex(QMutex::Recursive) |
13 | { |
14 | } |
15 | |
16 | GitCache::~GitCache() |
17 | { |
18 | clearInternalData(); |
19 | } |
20 | |
21 | void GitCache::setup(const WipRevisionInfo &wipInfo, QVector<CommitInfo> commits) |
22 | { |
23 | QMutexLocker lock(&mCommitsMutex); |
24 | |
25 | mInitialized = true; |
26 | |
27 | const auto totalCommits = commits.count() + 1; |
28 | |
29 | QLog_Debug("Cache" , QString("Configuring the cache for {%1} elements." ).arg(totalCommits)); |
30 | |
31 | mConfigured = false; |
32 | |
33 | mCommits.clear(); |
34 | mCommits.squeeze(); |
35 | mCommitsMap.clear(); |
36 | mCommitsMap.squeeze(); |
37 | mUntrackedFiles.clear(); |
38 | mUntrackedFiles.squeeze(); |
39 | mLanes.clear(); |
40 | |
41 | mCommitsMap.reserve(totalCommits); |
42 | mCommits.resize(totalCommits); |
43 | |
44 | QLog_Debug("Cache" , QString("Adding WIP revision." )); |
45 | |
46 | insertWipRevision(wipInfo); |
47 | |
48 | QLog_Debug("Cache" , QString("Adding committed revisions." )); |
49 | |
50 | QHash<QString, QVector<CommitInfo *>> tmpChildsStorage; |
51 | auto count = 0; |
52 | |
53 | for (auto &commit : commits) |
54 | { |
55 | calculateLanes(commit); |
56 | |
57 | const auto sha = commit.sha; |
58 | |
59 | if (sha == mCommitsMap.value(CommitInfo::ZERO_SHA).firstParent()) |
60 | commit.appendChild(&mCommitsMap[CommitInfo::ZERO_SHA]); |
61 | |
62 | mCommitsMap[sha] = commit; |
63 | mCommits[++count] = &mCommitsMap[sha]; |
64 | |
65 | if (tmpChildsStorage.contains(sha)) |
66 | { |
67 | for (const auto &child : qAsConst(tmpChildsStorage[sha])) |
68 | mCommitsMap[sha].appendChild(child); |
69 | |
70 | tmpChildsStorage.remove(sha); |
71 | } |
72 | |
73 | for (const auto &parent : qAsConst(mCommitsMap[sha].mParentsSha)) |
74 | tmpChildsStorage[parent].append(&mCommitsMap[sha]); |
75 | } |
76 | |
77 | mCommitsMap.squeeze(); |
78 | mCommits.squeeze(); |
79 | |
80 | tmpChildsStorage.clear(); |
81 | tmpChildsStorage.squeeze(); |
82 | } |
83 | |
84 | CommitInfo GitCache::commitInfo(int row) |
85 | { |
86 | QMutexLocker lock(&mCommitsMutex); |
87 | |
88 | const auto commit = row >= 0 && row < mCommits.count() ? mCommits.at(row) : nullptr; |
89 | |
90 | return commit ? *commit : CommitInfo(); |
91 | } |
92 | |
93 | auto GitCache::searchCommit(const QString &text, const int startingPoint) const |
94 | { |
95 | return std::find_if(mCommits.constBegin() + startingPoint, mCommits.constEnd(), |
96 | [text](CommitInfo *info) { return info->contains(text); }); |
97 | } |
98 | |
99 | auto GitCache::reverseSearchCommit(const QString &text, int startingPoint) const |
100 | { |
101 | const auto startEndPos = startingPoint > 0 ? mCommits.count() - startingPoint + 1 : 0; |
102 | |
103 | return std::find_if(mCommits.crbegin() + startEndPos, mCommits.crend(), |
104 | [text](CommitInfo *info) { return info->contains(text); }); |
105 | } |
106 | |
107 | CommitInfo GitCache::searchCommitInfo(const QString &text, int startingPoint, bool reverse) |
108 | { |
109 | QMutexLocker lock(&mCommitsMutex); |
110 | CommitInfo commit; |
111 | |
112 | if (!reverse) |
113 | { |
114 | auto commitIter = searchCommit(text, startingPoint); |
115 | |
116 | if (commitIter == mCommits.constEnd()) |
117 | commitIter = searchCommit(text); |
118 | |
119 | if (commitIter != mCommits.constEnd()) |
120 | commit = **commitIter; |
121 | } |
122 | else |
123 | { |
124 | auto commitIter = reverseSearchCommit(text, startingPoint); |
125 | |
126 | if (commitIter == mCommits.crend()) |
127 | commitIter = reverseSearchCommit(text); |
128 | |
129 | if (commitIter != mCommits.crend()) |
130 | commit = **commitIter; |
131 | } |
132 | |
133 | return commit; |
134 | } |
135 | |
136 | bool GitCache::isCommitInCurrentGeneologyTree(const QString &sha) |
137 | { |
138 | QMutexLocker lock(&mCommitsMutex); |
139 | |
140 | return checkSha(sha, CommitInfo::ZERO_SHA); |
141 | } |
142 | |
143 | CommitInfo GitCache::commitInfo(const QString &sha) |
144 | { |
145 | QMutexLocker lock(&mCommitsMutex); |
146 | |
147 | if (!sha.isEmpty()) |
148 | { |
149 | const auto c = mCommitsMap.value(sha, CommitInfo()); |
150 | |
151 | if (!c.isValid()) |
152 | { |
153 | const auto shas = mCommitsMap.keys(); |
154 | const auto it = std::find_if(shas.cbegin(), shas.cend(), |
155 | [sha](const QString &shaToCompare) { return shaToCompare.startsWith(sha); }); |
156 | |
157 | if (it != shas.cend()) |
158 | return mCommitsMap.value(*it); |
159 | |
160 | return CommitInfo(); |
161 | } |
162 | |
163 | return c; |
164 | } |
165 | |
166 | return CommitInfo(); |
167 | } |
168 | |
169 | std::optional<RevisionFiles> GitCache::revisionFile(const QString &sha1, const QString &sha2) const |
170 | { |
171 | QMutexLocker lock(&mRevisionsMutex); |
172 | |
173 | const auto iter = mRevisionFilesMap.constFind(qMakePair(sha1, sha2)); |
174 | |
175 | if (iter != mRevisionFilesMap.cend()) |
176 | return *iter; |
177 | |
178 | return std::nullopt; |
179 | } |
180 | |
181 | void GitCache::clearReferences() |
182 | { |
183 | QMutexLocker lock(&mReferencesMutex); |
184 | mReferences.clear(); |
185 | mReferences.squeeze(); |
186 | } |
187 | |
188 | void GitCache::insertWipRevision(const WipRevisionInfo &wipInfo) |
189 | { |
190 | auto newParentSha = wipInfo.parentSha; |
191 | |
192 | QLog_Debug("Cache" , QString("Updating the WIP commit. The actual parent has SHA {%1}." ).arg(newParentSha)); |
193 | |
194 | const auto fakeRevFile = fakeWorkDirRevFile(wipInfo.diffIndex, wipInfo.diffIndexCached); |
195 | |
196 | insertRevisionFile(CommitInfo::ZERO_SHA, newParentSha, fakeRevFile); |
197 | |
198 | QStringList parents; |
199 | |
200 | if (!newParentSha.isEmpty()) |
201 | parents.append(newParentSha); |
202 | |
203 | if (mLanes.isEmpty()) |
204 | mLanes.init(CommitInfo::ZERO_SHA); |
205 | |
206 | const auto log = fakeRevFile.count() == mUntrackedFiles.count() ? tr("No local changes" ) : tr("Local changes" ); |
207 | CommitInfo c(CommitInfo::ZERO_SHA, parents, std::chrono::seconds(QDateTime::currentSecsSinceEpoch()), log); |
208 | calculateLanes(c); |
209 | |
210 | if (mCommits[0]) |
211 | c.setLanes(mCommits[0]->lanes()); |
212 | |
213 | mCommitsMap.insert(CommitInfo::ZERO_SHA, std::move(c)); |
214 | mCommits[0] = &mCommitsMap[CommitInfo::ZERO_SHA]; |
215 | } |
216 | |
217 | bool GitCache::insertRevisionFiles(const QString &sha1, const QString &sha2, const RevisionFiles &file) |
218 | { |
219 | QMutexLocker lock(&mRevisionsMutex); |
220 | |
221 | return insertRevisionFile(sha1, sha2, file); |
222 | } |
223 | |
224 | bool GitCache::insertRevisionFile(const QString &sha1, const QString &sha2, const RevisionFiles &file) |
225 | { |
226 | const auto key = qMakePair(sha1, sha2); |
227 | const auto emptyShas = !sha1.isEmpty() && !sha2.isEmpty(); |
228 | const auto isWip = sha1 == CommitInfo::ZERO_SHA; |
229 | |
230 | if ((emptyShas || isWip) && mRevisionFilesMap.value(key) != file) |
231 | { |
232 | QLog_Debug("Cache" , QString("Adding the revisions files between {%1} and {%2}." ).arg(sha1, sha2)); |
233 | |
234 | mRevisionFilesMap.insert(key, file); |
235 | |
236 | return true; |
237 | } |
238 | |
239 | return false; |
240 | } |
241 | |
242 | void GitCache::insertReference(const QString &sha, References::Type type, const QString &reference) |
243 | { |
244 | QMutexLocker lock(&mReferencesMutex); |
245 | |
246 | QLog_Trace("Cache" , QString("Adding a new reference with SHA {%1}." ).arg(sha)); |
247 | |
248 | mReferences[sha].addReference(type, reference); |
249 | } |
250 | |
251 | void GitCache::deleteReference(const QString &sha, References::Type type, const QString &reference) |
252 | { |
253 | QMutexLocker lock(&mReferencesMutex); |
254 | |
255 | mReferences[sha].removeReference(type, reference); |
256 | } |
257 | |
258 | bool GitCache::hasReferences(const QString &sha) |
259 | { |
260 | QMutexLocker lock(&mReferencesMutex); |
261 | |
262 | return mReferences.contains(sha) && !mReferences.value(sha).isEmpty(); |
263 | } |
264 | |
265 | QStringList GitCache::getReferences(const QString &sha, References::Type type) |
266 | { |
267 | QMutexLocker lock(&mReferencesMutex); |
268 | |
269 | return mReferences.value(sha).getReferences(type); |
270 | } |
271 | |
272 | QString GitCache::getShaOfReference(const QString &referenceName, References::Type type) const |
273 | { |
274 | QMutexLocker lock(&mReferencesMutex); |
275 | |
276 | for (auto iter = mReferences.cbegin(); iter != mReferences.cend(); ++iter) |
277 | { |
278 | const auto references = iter.value().getReferences(type); |
279 | |
280 | for (const auto &reference : references) |
281 | if (reference == referenceName) |
282 | return iter.key(); |
283 | } |
284 | |
285 | return QString(); |
286 | } |
287 | |
288 | void GitCache::reloadCurrentBranchInfo(const QString ¤tBranch, const QString ¤tSha) |
289 | { |
290 | QMutexLocker lock(&mReferencesMutex); |
291 | |
292 | const auto lastItem = mReferences.end(); |
293 | for (auto ref = mReferences.begin(); ref != lastItem; ++ref) |
294 | { |
295 | if (ref.value().getReferences(References::Type::LocalBranch).contains(currentBranch)) |
296 | { |
297 | ref.value().removeReference(References::Type::LocalBranch, currentBranch); |
298 | |
299 | const auto key = ref.key(); |
300 | |
301 | if (mReferences.value(key).isEmpty()) |
302 | mReferences.remove(key); |
303 | |
304 | break; |
305 | } |
306 | } |
307 | |
308 | mReferences[currentSha].addReference(References::Type::LocalBranch, currentBranch); |
309 | } |
310 | |
311 | bool GitCache::updateWipCommit(const WipRevisionInfo &wipInfo) |
312 | { |
313 | QMutexLocker lock(&mRevisionsMutex); |
314 | QMutexLocker lock2(&mCommitsMutex); |
315 | |
316 | if (mConfigured) |
317 | { |
318 | insertWipRevision(wipInfo); |
319 | return true; |
320 | } |
321 | |
322 | return false; |
323 | } |
324 | |
325 | void GitCache::insertCommit(CommitInfo commit) |
326 | { |
327 | QMutexLocker lock2(&mCommitsMutex); |
328 | |
329 | const auto sha = commit.sha; |
330 | const auto parentSha = commit.firstParent(); |
331 | |
332 | commit.setLanes({ LaneType::ACTIVE }); |
333 | commit.pos = 1; |
334 | |
335 | mCommitsMap[sha] = std::move(commit); |
336 | mCommitsMap[sha].appendChild(&mCommitsMap[CommitInfo::ZERO_SHA]); |
337 | |
338 | mCommitsMap[parentSha].removeChild(&mCommitsMap[CommitInfo::ZERO_SHA]); |
339 | mCommitsMap[parentSha].appendChild(&mCommitsMap[sha]); |
340 | |
341 | const auto total = mCommits.count(); |
342 | for (auto i = 1; i < total; ++i) |
343 | ++mCommits[i]->pos; |
344 | |
345 | mCommits.insert(1, &mCommitsMap[sha]); |
346 | } |
347 | |
348 | void GitCache::updateCommit(const QString &oldSha, CommitInfo newCommit) |
349 | { |
350 | QMutexLocker lock(&mCommitsMutex); |
351 | QMutexLocker lock2(&mRevisionsMutex); |
352 | |
353 | auto &oldCommit = mCommitsMap[oldSha]; |
354 | const auto oldCommitParens = oldCommit.parents(); |
355 | const auto newCommitSha = newCommit.sha; |
356 | |
357 | mCommitsMap.remove(oldSha); |
358 | mCommitsMap.insert(newCommitSha, std::move(newCommit)); |
359 | mCommits[1] = &mCommitsMap[newCommitSha]; |
360 | |
361 | for (const auto &parent : oldCommitParens) |
362 | { |
363 | mCommitsMap[parent].removeChild(&oldCommit); |
364 | mCommitsMap[parent].appendChild(&mCommitsMap[newCommitSha]); |
365 | } |
366 | |
367 | const auto tags = getReferences(oldSha, References::Type::LocalTag); |
368 | for (const auto &tag : tags) |
369 | { |
370 | insertReference(newCommitSha, References::Type::LocalTag, tag); |
371 | deleteReference(oldSha, References::Type::LocalTag, tag); |
372 | } |
373 | |
374 | const auto localBranches = getReferences(oldSha, References::Type::LocalBranch); |
375 | for (const auto &branch : localBranches) |
376 | { |
377 | insertReference(newCommitSha, References::Type::LocalBranch, branch); |
378 | deleteReference(oldSha, References::Type::LocalBranch, branch); |
379 | } |
380 | } |
381 | |
382 | void GitCache::calculateLanes(CommitInfo &c) |
383 | { |
384 | const auto sha = c.sha; |
385 | |
386 | QLog_Trace("Cache" , QString("Updating the lanes for SHA {%1}." ).arg(sha)); |
387 | |
388 | bool isDiscontinuity; |
389 | bool isFork = mLanes.isFork(sha, isDiscontinuity); |
390 | bool isMerge = c.parentsCount() > 1; |
391 | |
392 | if (isDiscontinuity) |
393 | mLanes.changeActiveLane(sha); |
394 | |
395 | if (isFork) |
396 | mLanes.setFork(sha); |
397 | if (isMerge) |
398 | mLanes.setMerge(c.parents()); |
399 | if (c.parentsCount() == 0) |
400 | mLanes.setInitial(); |
401 | |
402 | const auto lanes = mLanes.getLanes(); |
403 | |
404 | resetLanes(c, isFork); |
405 | |
406 | c.setLanes(std::move(lanes)); |
407 | } |
408 | |
409 | bool GitCache::pendingLocalChanges() |
410 | { |
411 | QMutexLocker lock(&mCommitsMutex); |
412 | QMutexLocker lock2(&mRevisionsMutex); |
413 | |
414 | auto localChanges = false; |
415 | |
416 | if (const auto commit = mCommitsMap.value(CommitInfo::ZERO_SHA, CommitInfo()); commit.isValid()) |
417 | { |
418 | if (const auto rf = revisionFile(CommitInfo::ZERO_SHA, commit.firstParent()); rf) |
419 | localChanges = rf.value().count() - mUntrackedFiles.count() > 0; |
420 | } |
421 | |
422 | return localChanges; |
423 | } |
424 | |
425 | QVector<QPair<QString, QStringList>> GitCache::getBranches(References::Type type) |
426 | { |
427 | QMutexLocker lock(&mReferencesMutex); |
428 | QVector<QPair<QString, QStringList>> branches; |
429 | |
430 | for (auto iter = mReferences.cbegin(); iter != mReferences.cend(); ++iter) |
431 | branches.append(QPair<QString, QStringList>(iter.key(), iter.value().getReferences(type))); |
432 | |
433 | return branches; |
434 | } |
435 | |
436 | QMap<QString, QString> GitCache::getTags(References::Type tagType) const |
437 | { |
438 | QMutexLocker lock(&mReferencesMutex); |
439 | |
440 | QMap<QString, QString> tags; |
441 | |
442 | for (auto iter = mReferences.cbegin(); iter != mReferences.cend(); ++iter) |
443 | { |
444 | const auto tagNames = iter->getReferences(tagType); |
445 | |
446 | for (const auto &tag : tagNames) |
447 | tags[tag] = iter.key(); |
448 | } |
449 | |
450 | return tags; |
451 | } |
452 | |
453 | void GitCache::updateTags(QMap<QString, QString> remoteTags) |
454 | { |
455 | const auto end = remoteTags.cend(); |
456 | |
457 | for (auto iter = remoteTags.cbegin(); iter != end; ++iter) |
458 | insertReference(iter.value(), References::Type::RemoteTag, iter.key()); |
459 | |
460 | emit signalCacheUpdated(); |
461 | } |
462 | |
463 | void GitCache::resetLanes(const CommitInfo &c, bool isFork) |
464 | { |
465 | const auto nextSha = c.parentsCount() == 0 ? QString() : c.firstParent(); |
466 | |
467 | mLanes.nextParent(nextSha); |
468 | |
469 | if (c.parentsCount() > 1) |
470 | mLanes.afterMerge(); |
471 | if (isFork) |
472 | mLanes.afterFork(); |
473 | if (mLanes.isBranch()) |
474 | mLanes.afterBranch(); |
475 | } |
476 | |
477 | bool GitCache::checkSha(const QString &originalSha, const QString ¤tSha) const |
478 | { |
479 | if (originalSha == currentSha) |
480 | return true; |
481 | |
482 | if (const auto iter = mCommitsMap.find(currentSha); iter != mCommitsMap.cend()) |
483 | return checkSha(originalSha, iter->firstParent()); |
484 | |
485 | return false; |
486 | } |
487 | |
488 | void GitCache::clearInternalData() |
489 | { |
490 | mCommits.clear(); |
491 | mCommits.squeeze(); |
492 | mCommitsMap.clear(); |
493 | mCommitsMap.squeeze(); |
494 | mReferences.clear(); |
495 | mRevisionFilesMap.clear(); |
496 | mRevisionFilesMap.squeeze(); |
497 | mUntrackedFiles.clear(); |
498 | mUntrackedFiles.squeeze(); |
499 | mLanes.clear(); |
500 | mReferences.clear(); |
501 | mReferences.squeeze(); |
502 | } |
503 | |
504 | int GitCache::commitCount() const |
505 | { |
506 | return mCommits.count(); |
507 | } |
508 | |
509 | RevisionFiles GitCache::fakeWorkDirRevFile(const QString &diffIndex, const QString &diffIndexCache) |
510 | { |
511 | RevisionFiles rf(diffIndex); |
512 | rf.setOnlyModified(false); |
513 | |
514 | for (const auto &it : qAsConst(mUntrackedFiles)) |
515 | { |
516 | rf.mFiles.append(it); |
517 | rf.setStatus(RevisionFiles::UNKNOWN); |
518 | rf.mergeParent.append(1); |
519 | } |
520 | |
521 | RevisionFiles cachedFiles(diffIndexCache, true); |
522 | |
523 | for (auto i = 0; i < rf.count(); i++) |
524 | { |
525 | if (const auto cachedIndex = cachedFiles.mFiles.indexOf(rf.getFile(i)); cachedIndex != -1) |
526 | { |
527 | if (cachedFiles.statusCmp(cachedIndex, RevisionFiles::CONFLICT)) |
528 | rf.appendStatus(i, RevisionFiles::CONFLICT); |
529 | else if (cachedFiles.statusCmp(cachedIndex, RevisionFiles::MODIFIED) |
530 | && cachedFiles.statusCmp(cachedIndex, RevisionFiles::IN_INDEX)) |
531 | rf.appendStatus(i, RevisionFiles::PARTIALLY_CACHED); |
532 | else if (cachedFiles.statusCmp(cachedIndex, RevisionFiles::IN_INDEX)) |
533 | rf.appendStatus(i, RevisionFiles::IN_INDEX); |
534 | } |
535 | } |
536 | |
537 | return rf; |
538 | } |
539 | |
540 | void GitCache::setUntrackedFilesList(QVector<QString> untrackedFiles) |
541 | { |
542 | mUntrackedFiles.clear(); |
543 | mUntrackedFiles.squeeze(); |
544 | mUntrackedFiles = std::move(untrackedFiles); |
545 | } |
546 | |