| 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 | |