1#include "GitCache.h"
2
3#include <QLogger.h>
4#include <WipRevisionInfo.h>
5
6using namespace QLogger;
7
8GitCache::GitCache(QObject *parent)
9 : QObject(parent)
10 , mCommitsMutex(QMutex::Recursive)
11 , mRevisionsMutex(QMutex::Recursive)
12 , mReferencesMutex(QMutex::Recursive)
13{
14}
15
16GitCache::~GitCache()
17{
18 clearInternalData();
19}
20
21void 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
84CommitInfo 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
93auto 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
99auto 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
107CommitInfo 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
136bool GitCache::isCommitInCurrentGeneologyTree(const QString &sha)
137{
138 QMutexLocker lock(&mCommitsMutex);
139
140 return checkSha(sha, CommitInfo::ZERO_SHA);
141}
142
143CommitInfo 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
169std::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
181void GitCache::clearReferences()
182{
183 QMutexLocker lock(&mReferencesMutex);
184 mReferences.clear();
185 mReferences.squeeze();
186}
187
188void 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
217bool 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
224bool 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
242void 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
251void 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
258bool GitCache::hasReferences(const QString &sha)
259{
260 QMutexLocker lock(&mReferencesMutex);
261
262 return mReferences.contains(sha) && !mReferences.value(sha).isEmpty();
263}
264
265QStringList GitCache::getReferences(const QString &sha, References::Type type)
266{
267 QMutexLocker lock(&mReferencesMutex);
268
269 return mReferences.value(sha).getReferences(type);
270}
271
272QString 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
288void GitCache::reloadCurrentBranchInfo(const QString &currentBranch, const QString &currentSha)
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
311bool 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
325void 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
348void 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
382void 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
409bool 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
425QVector<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
436QMap<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
453void 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
463void 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
477bool GitCache::checkSha(const QString &originalSha, const QString &currentSha) 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
488void 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
504int GitCache::commitCount() const
505{
506 return mCommits.count();
507}
508
509RevisionFiles 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
540void GitCache::setUntrackedFilesList(QVector<QString> untrackedFiles)
541{
542 mUntrackedFiles.clear();
543 mUntrackedFiles.squeeze();
544 mUntrackedFiles = std::move(untrackedFiles);
545}
546