| 1 | #include "CommitHistoryContextMenu.h" |
| 2 | |
| 3 | #include <BranchDlg.h> |
| 4 | #include <CommitInfo.h> |
| 5 | //#include <CreateIssueDlg.h> |
| 6 | //#include <CreatePullRequestDlg.h> |
| 7 | #include <GitBase.h> |
| 8 | #include <GitBranches.h> |
| 9 | #include <GitCache.h> |
| 10 | #include <GitConfig.h> |
| 11 | #include <GitHistory.h> |
| 12 | #include <GitHubRestApi.h> |
| 13 | #include <GitLocal.h> |
| 14 | #include <GitPatches.h> |
| 15 | #include <GitQlientStyles.h> |
| 16 | #include <GitRemote.h> |
| 17 | #include <GitServerCache.h> |
| 18 | #include <GitStashes.h> |
| 19 | #include <GitTags.h> |
| 20 | #include <MergePullRequestDlg.h> |
| 21 | #include <PullDlg.h> |
| 22 | #include <SquashDlg.h> |
| 23 | #include <TagDlg.h> |
| 24 | |
| 25 | #include <QApplication> |
| 26 | #include <QClipboard> |
| 27 | #include <QDesktopServices> |
| 28 | #include <QFileDialog> |
| 29 | #include <QMessageBox> |
| 30 | #include <QProcess> |
| 31 | |
| 32 | #include <QLogger.h> |
| 33 | |
| 34 | using namespace QLogger; |
| 35 | |
| 36 | CommitHistoryContextMenu::(const QSharedPointer<GitCache> &cache, |
| 37 | const QSharedPointer<GitBase> &git, |
| 38 | const QSharedPointer<GitServerCache> &gitServerCache, |
| 39 | const QStringList &shas, QWidget *parent) |
| 40 | : QMenu(parent) |
| 41 | , mCache(cache) |
| 42 | , mGit(git) |
| 43 | , mGitServerCache(gitServerCache) |
| 44 | , mGitTags(new GitTags(mGit, mCache)) |
| 45 | , mShas(shas) |
| 46 | { |
| 47 | setAttribute(Qt::WA_DeleteOnClose); |
| 48 | |
| 49 | if (shas.count() == 1) |
| 50 | createIndividualShaMenu(); |
| 51 | else |
| 52 | createMultipleShasMenu(); |
| 53 | } |
| 54 | |
| 55 | void CommitHistoryContextMenu::() |
| 56 | { |
| 57 | const auto singleSelection = mShas.count() == 1; |
| 58 | |
| 59 | if (singleSelection) |
| 60 | { |
| 61 | const auto sha = mShas.first(); |
| 62 | |
| 63 | if (sha == CommitInfo::ZERO_SHA) |
| 64 | { |
| 65 | const auto = addMenu(tr("Stash" )); |
| 66 | const auto stashAction = stashMenu->addAction(tr("Push" )); |
| 67 | connect(stashAction, &QAction::triggered, this, &CommitHistoryContextMenu::stashPush); |
| 68 | |
| 69 | const auto popAction = stashMenu->addAction(tr("Pop" )); |
| 70 | connect(popAction, &QAction::triggered, this, &CommitHistoryContextMenu::stashPop); |
| 71 | } |
| 72 | |
| 73 | const auto commitAction = addAction(tr("See diff" )); |
| 74 | connect(commitAction, &QAction::triggered, this, [this]() { emit signalOpenDiff(mShas.first()); }); |
| 75 | |
| 76 | if (sha != CommitInfo::ZERO_SHA) |
| 77 | { |
| 78 | const auto = addMenu(tr("Create" )); |
| 79 | |
| 80 | const auto createBranchAction = createMenu->addAction(tr("Branch" )); |
| 81 | connect(createBranchAction, &QAction::triggered, this, &CommitHistoryContextMenu::createBranch); |
| 82 | |
| 83 | const auto createTagAction = createMenu->addAction(tr("Tag" )); |
| 84 | connect(createTagAction, &QAction::triggered, this, &CommitHistoryContextMenu::createTag); |
| 85 | |
| 86 | const auto exportAsPatchAction = addAction(tr("Export as patch" )); |
| 87 | connect(exportAsPatchAction, &QAction::triggered, this, &CommitHistoryContextMenu::exportAsPatch); |
| 88 | |
| 89 | addSeparator(); |
| 90 | |
| 91 | const auto checkoutCommitAction = addAction(tr("Checkout commit" )); |
| 92 | connect(checkoutCommitAction, &QAction::triggered, this, &CommitHistoryContextMenu::checkoutCommit); |
| 93 | |
| 94 | addBranchActions(sha); |
| 95 | |
| 96 | QScopedPointer<GitBranches> git(new GitBranches(mGit)); |
| 97 | |
| 98 | if (auto ret = git->getLastCommitOfBranch(mGit->getCurrentBranch()); ret.success) |
| 99 | { |
| 100 | const auto lastShaStr = ret.output.trimmed(); |
| 101 | |
| 102 | if (lastShaStr == sha) |
| 103 | { |
| 104 | const auto amendCommitAction = addAction(tr("Amend" )); |
| 105 | connect(amendCommitAction, &QAction::triggered, this, |
| 106 | [this]() { emit signalAmendCommit(mShas.first()); }); |
| 107 | |
| 108 | const auto = addMenu(tr("Apply" )); |
| 109 | |
| 110 | const auto applyPatchAction = applyMenu->addAction(tr("Patch" )); |
| 111 | connect(applyPatchAction, &QAction::triggered, this, &CommitHistoryContextMenu::applyPatch); |
| 112 | |
| 113 | const auto applyCommitAction = applyMenu->addAction(tr("Commit" )); |
| 114 | connect(applyCommitAction, &QAction::triggered, this, &CommitHistoryContextMenu::applyCommit); |
| 115 | |
| 116 | const auto pushAction = addAction(tr("Push" )); |
| 117 | connect(pushAction, &QAction::triggered, this, &CommitHistoryContextMenu::push); |
| 118 | |
| 119 | const auto pullAction = addAction(tr("Pull" )); |
| 120 | connect(pullAction, &QAction::triggered, this, &CommitHistoryContextMenu::pull); |
| 121 | |
| 122 | const auto fetchAction = addAction(tr("Fetch" )); |
| 123 | connect(fetchAction, &QAction::triggered, this, &CommitHistoryContextMenu::fetch); |
| 124 | } |
| 125 | else if (mCache->isCommitInCurrentGeneologyTree(mShas.first())) |
| 126 | { |
| 127 | const auto pushAction = addAction(tr("Push" )); |
| 128 | connect(pushAction, &QAction::triggered, this, &CommitHistoryContextMenu::push); |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | const auto = addMenu(tr("Reset" )); |
| 133 | |
| 134 | const auto resetSoftAction = resetMenu->addAction(tr("Soft" )); |
| 135 | connect(resetSoftAction, &QAction::triggered, this, &CommitHistoryContextMenu::resetSoft); |
| 136 | |
| 137 | const auto resetMixedAction = resetMenu->addAction(tr("Mixed" )); |
| 138 | connect(resetMixedAction, &QAction::triggered, this, &CommitHistoryContextMenu::resetMixed); |
| 139 | |
| 140 | const auto resetHardAction = resetMenu->addAction(tr("Hard" )); |
| 141 | connect(resetHardAction, &QAction::triggered, this, &CommitHistoryContextMenu::resetHard); |
| 142 | |
| 143 | addSeparator(); |
| 144 | |
| 145 | const auto = addMenu(tr("Copy" )); |
| 146 | |
| 147 | const auto copyShaAction = copyMenu->addAction(tr("Commit SHA" )); |
| 148 | connect(copyShaAction, &QAction::triggered, this, |
| 149 | [this]() { QApplication::clipboard()->setText(mShas.first()); }); |
| 150 | |
| 151 | const auto copyTitleAction = copyMenu->addAction(tr("Commit title" )); |
| 152 | connect(copyTitleAction, &QAction::triggered, this, [this]() { |
| 153 | const auto title = mCache->commitInfo(mShas.first()).shortLog; |
| 154 | QApplication::clipboard()->setText(title); |
| 155 | }); |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | if (mGitServerCache) |
| 160 | { |
| 161 | const auto isGitHub = mGitServerCache->getPlatform() == GitServer::Platform::GitHub; |
| 162 | const auto = new QMenu(QString::fromUtf8(isGitHub ? "GitHub" : "GitLab" ), this); |
| 163 | |
| 164 | addSeparator(); |
| 165 | addMenu(gitServerMenu); |
| 166 | |
| 167 | if (const auto pr = mGitServerCache->getPullRequest(mShas.first()); singleSelection && pr.isValid()) |
| 168 | { |
| 169 | const auto prInfo = mGitServerCache->getPullRequest(mShas.first()); |
| 170 | |
| 171 | const auto = new QMenu("Checks" , gitServerMenu); |
| 172 | gitServerMenu->addMenu(checksMenu); |
| 173 | |
| 174 | for (const auto &check : prInfo.state.checks) |
| 175 | { |
| 176 | const auto link = check.url; |
| 177 | checksMenu->addAction(QIcon(QString(":/icons/%1" ).arg(check.state)), check.name, this, |
| 178 | [link]() { QDesktopServices::openUrl(link); }); |
| 179 | } |
| 180 | |
| 181 | if (isGitHub) |
| 182 | { |
| 183 | connect(gitServerMenu->addAction(tr("Merge PR" )), &QAction::triggered, this, [this, pr]() { |
| 184 | const auto mergeDlg = new MergePullRequestDlg(mGit, pr, mShas.first(), this); |
| 185 | connect(mergeDlg, &MergePullRequestDlg::signalRepositoryUpdated, this, |
| 186 | &CommitHistoryContextMenu::fullReload); |
| 187 | |
| 188 | mergeDlg->exec(); |
| 189 | }); |
| 190 | } |
| 191 | |
| 192 | connect(gitServerMenu->addAction(tr("Show PR detailed view" )), &QAction::triggered, this, |
| 193 | [this, num = pr.number]() { emit showPrDetailedView(num); }); |
| 194 | |
| 195 | gitServerMenu->addSeparator(); |
| 196 | } |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | void CommitHistoryContextMenu::() |
| 201 | { |
| 202 | if (mShas.count() == 2) |
| 203 | { |
| 204 | const auto diffAction = addAction(tr("See diff" )); |
| 205 | connect(diffAction, &QAction::triggered, this, [this]() { emit signalOpenCompareDiff(mShas); }); |
| 206 | } |
| 207 | |
| 208 | if (!mShas.contains(CommitInfo::ZERO_SHA)) |
| 209 | { |
| 210 | const auto exportAsPatchAction = addAction(tr("Export as patch" )); |
| 211 | connect(exportAsPatchAction, &QAction::triggered, this, &CommitHistoryContextMenu::exportAsPatch); |
| 212 | |
| 213 | const auto copyShaAction = addAction(tr("Copy all SHA" )); |
| 214 | connect(copyShaAction, &QAction::triggered, this, |
| 215 | [this]() { QApplication::clipboard()->setText(mShas.join(',')); }); |
| 216 | |
| 217 | auto shasInCurrenTree = 0; |
| 218 | |
| 219 | for (const auto &sha : qAsConst(mShas)) |
| 220 | shasInCurrenTree += mCache->isCommitInCurrentGeneologyTree(sha); |
| 221 | |
| 222 | if (shasInCurrenTree == 0) |
| 223 | { |
| 224 | const auto cherryPickAction = addAction(tr("Cherry pick ALL commits" )); |
| 225 | connect(cherryPickAction, &QAction::triggered, this, &CommitHistoryContextMenu::cherryPickCommit); |
| 226 | } |
| 227 | else if (shasInCurrenTree == mShas.count()) |
| 228 | { |
| 229 | const auto cherryPickAction = addAction(tr("Squash commits" )); |
| 230 | connect(cherryPickAction, &QAction::triggered, this, &CommitHistoryContextMenu::showSquashDialog); |
| 231 | } |
| 232 | } |
| 233 | else |
| 234 | QLog_Warning("UI" , "WIP selected as part of a series of SHAs" ); |
| 235 | } |
| 236 | |
| 237 | void CommitHistoryContextMenu::() |
| 238 | { |
| 239 | QScopedPointer<GitStashes> git(new GitStashes(mGit)); |
| 240 | const auto ret = git->stash(); |
| 241 | |
| 242 | if (ret.success) |
| 243 | emit logReload(); |
| 244 | } |
| 245 | |
| 246 | void CommitHistoryContextMenu::() |
| 247 | { |
| 248 | QScopedPointer<GitStashes> git(new GitStashes(mGit)); |
| 249 | const auto ret = git->pop(); |
| 250 | |
| 251 | if (ret.success) |
| 252 | emit logReload(); |
| 253 | } |
| 254 | |
| 255 | void CommitHistoryContextMenu::() |
| 256 | { |
| 257 | BranchDlg dlg({ mShas.first(), BranchDlgMode::CREATE_FROM_COMMIT, mCache, mGit }); |
| 258 | dlg.exec(); |
| 259 | } |
| 260 | |
| 261 | void CommitHistoryContextMenu::() |
| 262 | { |
| 263 | TagDlg dlg(QSharedPointer<GitBase>::create(mGit->getWorkingDir()), mShas.first()); |
| 264 | const auto ret = dlg.exec(); |
| 265 | |
| 266 | if (ret == QDialog::Accepted) |
| 267 | emit referencesReload(); // TODO: Optimize |
| 268 | } |
| 269 | |
| 270 | void CommitHistoryContextMenu::() |
| 271 | { |
| 272 | QScopedPointer<GitPatches> git(new GitPatches(mGit)); |
| 273 | const auto ret = git->exportPatch(mShas); |
| 274 | |
| 275 | if (ret.success) |
| 276 | { |
| 277 | const auto action = QMessageBox::information(this, tr("Patch generated" ), |
| 278 | tr("<p>The patch has been generated!</p>" |
| 279 | "<p><b>Commit:</b></p><p>%1</p>" |
| 280 | "<p><b>Destination:</b> %2</p>" |
| 281 | "<p><b>File names:</b></p><p>%3</p>" ) |
| 282 | .arg(mShas.join("<br>" ), mGit->getWorkingDir(), ret.output), |
| 283 | QMessageBox::Ok, QMessageBox::Open); |
| 284 | |
| 285 | if (action == QMessageBox::Open) |
| 286 | { |
| 287 | QString fileBrowser; |
| 288 | |
| 289 | #ifdef Q_OS_LINUX |
| 290 | fileBrowser.append("xdg-open" ); |
| 291 | #elif defined(Q_OS_WIN) |
| 292 | fileBrowser.append("explorer.exe" ); |
| 293 | #endif |
| 294 | |
| 295 | QProcess::startDetached(fileBrowser, { mGit->getWorkingDir() }); |
| 296 | } |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | void CommitHistoryContextMenu::() |
| 301 | { |
| 302 | const auto action = qobject_cast<QAction *>(sender()); |
| 303 | auto isLocal = action->data().toBool(); |
| 304 | auto branchName = action->text(); |
| 305 | |
| 306 | if (isLocal) |
| 307 | branchName.remove("origin/" ); |
| 308 | |
| 309 | QScopedPointer<GitBranches> git(new GitBranches(mGit)); |
| 310 | const auto ret = isLocal ? git->checkoutLocalBranch(branchName) : git->checkoutRemoteBranch(branchName); |
| 311 | const auto output = ret.output; |
| 312 | |
| 313 | if (ret.success) |
| 314 | { |
| 315 | QRegExp rx("by \\d+ commits" ); |
| 316 | rx.indexIn(ret.output); |
| 317 | auto value = rx.capturedTexts().constFirst().split(" " ); |
| 318 | |
| 319 | if (value.count() == 3 && output.contains("your branch is behind" , Qt::CaseInsensitive)) |
| 320 | { |
| 321 | const auto commits = value.at(1).toUInt(); |
| 322 | (void)commits; |
| 323 | |
| 324 | PullDlg pull(mGit, output.split('\n').first()); |
| 325 | connect(&pull, &PullDlg::signalRepositoryUpdated, this, &CommitHistoryContextMenu::fullReload); |
| 326 | connect(&pull, &PullDlg::signalPullConflict, this, &CommitHistoryContextMenu::signalPullConflict); |
| 327 | } |
| 328 | |
| 329 | emit logReload(); |
| 330 | } |
| 331 | else |
| 332 | { |
| 333 | QMessageBox msgBox(QMessageBox::Critical, tr("Error while checking out" ), |
| 334 | tr("There were problems during the checkout operation. Please, see the detailed " |
| 335 | "description for more information." ), |
| 336 | QMessageBox::Ok, this); |
| 337 | msgBox.setDetailedText(ret.output); |
| 338 | msgBox.setStyleSheet(GitQlientStyles::getStyles()); |
| 339 | msgBox.exec(); |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | void CommitHistoryContextMenu::() |
| 344 | { |
| 345 | BranchDlg dlg({ mShas.constFirst(), BranchDlgMode::CREATE_CHECKOUT_FROM_COMMIT, mCache, mGit }); |
| 346 | dlg.exec(); |
| 347 | } |
| 348 | |
| 349 | void CommitHistoryContextMenu::() |
| 350 | { |
| 351 | const auto sha = mShas.first(); |
| 352 | QLog_Info("UI" , QString("Checking out the commit {%1}" ).arg(sha)); |
| 353 | |
| 354 | QScopedPointer<GitLocal> git(new GitLocal(mGit)); |
| 355 | const auto ret = git->checkoutCommit(sha); |
| 356 | |
| 357 | if (ret.success) |
| 358 | emit logReload(); |
| 359 | else |
| 360 | { |
| 361 | QMessageBox msgBox(QMessageBox::Critical, tr("Error while checking out" ), |
| 362 | tr("There were problems during the checkout operation. Please, see the detailed " |
| 363 | "description for more information." ), |
| 364 | QMessageBox::Ok, this); |
| 365 | msgBox.setDetailedText(ret.output); |
| 366 | msgBox.setStyleSheet(GitQlientStyles::getStyles()); |
| 367 | msgBox.exec(); |
| 368 | } |
| 369 | } |
| 370 | |
| 371 | void CommitHistoryContextMenu::() |
| 372 | { |
| 373 | auto shas = mShas; |
| 374 | for (const auto &sha : qAsConst(mShas)) |
| 375 | { |
| 376 | const auto lastShaBeforeCommit = mGit->getLastCommit().output.trimmed(); |
| 377 | QScopedPointer<GitLocal> git(new GitLocal(mGit)); |
| 378 | const auto ret = git->cherryPickCommit(sha); |
| 379 | |
| 380 | shas.takeFirst(); |
| 381 | |
| 382 | if (ret.success && shas.isEmpty()) |
| 383 | { |
| 384 | auto commit = mCache->commitInfo(sha); |
| 385 | commit.sha = mGit->getLastCommit().output.trimmed(); |
| 386 | |
| 387 | mCache->insertCommit(commit); |
| 388 | mCache->deleteReference(lastShaBeforeCommit, References::Type::LocalBranch, mGit->getCurrentBranch()); |
| 389 | mCache->insertReference(commit.sha, References::Type::LocalBranch, mGit->getCurrentBranch()); |
| 390 | |
| 391 | QScopedPointer<GitHistory> gitHistory(new GitHistory(mGit)); |
| 392 | const auto ret = gitHistory->getDiffFiles(commit.sha, lastShaBeforeCommit); |
| 393 | |
| 394 | mCache->insertRevisionFiles(commit.sha, lastShaBeforeCommit, RevisionFiles(ret.output)); |
| 395 | |
| 396 | emit mCache->signalCacheUpdated(); |
| 397 | emit logReload(); |
| 398 | } |
| 399 | else if (!ret.success) |
| 400 | { |
| 401 | const auto errorMsg = ret.output; |
| 402 | |
| 403 | if (errorMsg.contains("error: could not apply" , Qt::CaseInsensitive) |
| 404 | && errorMsg.contains("after resolving the conflicts" , Qt::CaseInsensitive)) |
| 405 | { |
| 406 | emit signalCherryPickConflict(shas); |
| 407 | } |
| 408 | else |
| 409 | { |
| 410 | QMessageBox msgBox(QMessageBox::Critical, tr("Error while cherry-pick" ), |
| 411 | tr("There were problems during the cherry-pich operation. Please, see the detailed " |
| 412 | "description for more information." ), |
| 413 | QMessageBox::Ok, this); |
| 414 | msgBox.setDetailedText(errorMsg); |
| 415 | msgBox.setStyleSheet(GitQlientStyles::getStyles()); |
| 416 | msgBox.exec(); |
| 417 | } |
| 418 | } |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | void CommitHistoryContextMenu::() |
| 423 | { |
| 424 | const QString fileName(QFileDialog::getOpenFileName(this, tr("Select a patch to apply" ))); |
| 425 | QScopedPointer<GitPatches> git(new GitPatches(mGit)); |
| 426 | |
| 427 | if (!fileName.isEmpty() && git->applyPatch(fileName)) |
| 428 | emit logReload(); |
| 429 | } |
| 430 | |
| 431 | void CommitHistoryContextMenu::() |
| 432 | { |
| 433 | const QString fileName(QFileDialog::getOpenFileName(this, "Select a patch to apply" )); |
| 434 | QScopedPointer<GitPatches> git(new GitPatches(mGit)); |
| 435 | |
| 436 | if (!fileName.isEmpty() && git->applyPatch(fileName, true)) |
| 437 | emit logReload(); |
| 438 | } |
| 439 | |
| 440 | void CommitHistoryContextMenu::() |
| 441 | { |
| 442 | QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); |
| 443 | QScopedPointer<GitRemote> git(new GitRemote(mGit)); |
| 444 | const auto ret = git->pushCommit(mShas.first(), mGit->getCurrentBranch()); |
| 445 | QApplication::restoreOverrideCursor(); |
| 446 | |
| 447 | if (ret.output.contains("has no upstream branch" )) |
| 448 | { |
| 449 | const auto currentBranch = mGit->getCurrentBranch(); |
| 450 | BranchDlg dlg({ currentBranch, BranchDlgMode::PUSH_UPSTREAM, mCache, mGit }); |
| 451 | const auto ret = dlg.exec(); |
| 452 | |
| 453 | if (ret == QDialog::Accepted) |
| 454 | emit signalRefreshPRsCache(); |
| 455 | } |
| 456 | else if (ret.success) |
| 457 | { |
| 458 | const auto currentBranch = mGit->getCurrentBranch(); |
| 459 | QScopedPointer<GitConfig> git(new GitConfig(mGit)); |
| 460 | const auto remote = git->getRemoteForBranch(currentBranch); |
| 461 | |
| 462 | if (remote.success) |
| 463 | { |
| 464 | const auto oldSha = mCache->getShaOfReference(QString("%1/%2" ).arg(remote.output, currentBranch), |
| 465 | References::Type::RemoteBranches); |
| 466 | const auto sha = mCache->getShaOfReference(currentBranch, References::Type::LocalBranch); |
| 467 | mCache->deleteReference(oldSha, References::Type::RemoteBranches, |
| 468 | QString("%1/%2" ).arg(remote.output, currentBranch)); |
| 469 | mCache->insertReference(sha, References::Type::RemoteBranches, |
| 470 | QString("%1/%2" ).arg(remote.output, currentBranch)); |
| 471 | emit mCache->signalCacheUpdated(); |
| 472 | emit signalRefreshPRsCache(); |
| 473 | } |
| 474 | } |
| 475 | else |
| 476 | { |
| 477 | QMessageBox msgBox(QMessageBox::Critical, tr("Error while pushing" ), |
| 478 | tr("There were problems during the push operation. Please, see the detailed description " |
| 479 | "for more information." ), |
| 480 | QMessageBox::Ok, this); |
| 481 | msgBox.setDetailedText(ret.output); |
| 482 | msgBox.setStyleSheet(GitQlientStyles::getStyles()); |
| 483 | msgBox.exec(); |
| 484 | } |
| 485 | } |
| 486 | |
| 487 | void CommitHistoryContextMenu::() |
| 488 | { |
| 489 | QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); |
| 490 | QScopedPointer<GitRemote> git(new GitRemote(mGit)); |
| 491 | const auto ret = git->pull(); |
| 492 | QApplication::restoreOverrideCursor(); |
| 493 | |
| 494 | if (ret.success) |
| 495 | emit fullReload(); |
| 496 | else |
| 497 | { |
| 498 | const auto errorMsg = ret.output; |
| 499 | |
| 500 | if (errorMsg.contains("error: could not apply" , Qt::CaseInsensitive) |
| 501 | && errorMsg.contains("causing a conflict" , Qt::CaseInsensitive)) |
| 502 | { |
| 503 | emit signalPullConflict(); |
| 504 | } |
| 505 | else |
| 506 | { |
| 507 | QMessageBox msgBox(QMessageBox::Critical, tr("Error while pulling" ), |
| 508 | tr("There were problems during the pull operation. Please, see the detailed " |
| 509 | "description for more information." ), |
| 510 | QMessageBox::Ok, this); |
| 511 | msgBox.setDetailedText(errorMsg); |
| 512 | msgBox.setStyleSheet(GitQlientStyles::getStyles()); |
| 513 | msgBox.exec(); |
| 514 | } |
| 515 | } |
| 516 | } |
| 517 | |
| 518 | void CommitHistoryContextMenu::() |
| 519 | { |
| 520 | QScopedPointer<GitRemote> git(new GitRemote(mGit)); |
| 521 | |
| 522 | if (git->fetch()) |
| 523 | { |
| 524 | mGitTags->getRemoteTags(); |
| 525 | emit fullReload(); |
| 526 | } |
| 527 | } |
| 528 | |
| 529 | void CommitHistoryContextMenu::() |
| 530 | { |
| 531 | QScopedPointer<GitLocal> git(new GitLocal(mGit)); |
| 532 | const auto previousSha = mGit->getLastCommit().output.trimmed(); |
| 533 | |
| 534 | if (git->resetCommit(mShas.first(), GitLocal::CommitResetType::SOFT)) |
| 535 | { |
| 536 | mCache->deleteReference(previousSha, References::Type::LocalBranch, mGit->getCurrentBranch()); |
| 537 | mCache->insertReference(mShas.first(), References::Type::LocalBranch, mGit->getCurrentBranch()); |
| 538 | |
| 539 | emit logReload(); |
| 540 | } |
| 541 | } |
| 542 | |
| 543 | void CommitHistoryContextMenu::() |
| 544 | { |
| 545 | QScopedPointer<GitLocal> git(new GitLocal(mGit)); |
| 546 | const auto previousSha = mGit->getLastCommit().output.trimmed(); |
| 547 | |
| 548 | if (git->resetCommit(mShas.first(), GitLocal::CommitResetType::MIXED)) |
| 549 | { |
| 550 | mCache->deleteReference(previousSha, References::Type::LocalBranch, mGit->getCurrentBranch()); |
| 551 | mCache->insertReference(mShas.first(), References::Type::LocalBranch, mGit->getCurrentBranch()); |
| 552 | |
| 553 | emit logReload(); |
| 554 | } |
| 555 | } |
| 556 | |
| 557 | void CommitHistoryContextMenu::() |
| 558 | { |
| 559 | const auto retMsg = QMessageBox::warning( |
| 560 | this, "Reset hard requested!" , "Are you sure you want to reset the branch to this commit in a <b>hard</b> way?" , |
| 561 | QMessageBox::Ok, QMessageBox::Cancel); |
| 562 | |
| 563 | if (retMsg == QMessageBox::Ok) |
| 564 | { |
| 565 | const auto previousSha = mGit->getLastCommit().output.trimmed(); |
| 566 | QScopedPointer<GitLocal> git(new GitLocal(mGit)); |
| 567 | |
| 568 | if (git->resetCommit(mShas.first(), GitLocal::CommitResetType::HARD)) |
| 569 | { |
| 570 | mCache->deleteReference(previousSha, References::Type::LocalBranch, mGit->getCurrentBranch()); |
| 571 | mCache->insertReference(mShas.first(), References::Type::LocalBranch, mGit->getCurrentBranch()); |
| 572 | |
| 573 | emit logReload(); |
| 574 | } |
| 575 | } |
| 576 | } |
| 577 | |
| 578 | void CommitHistoryContextMenu::() |
| 579 | { |
| 580 | const auto action = qobject_cast<QAction *>(sender()); |
| 581 | const auto fromBranch = action->data().toString(); |
| 582 | |
| 583 | QScopedPointer<GitRemote> git(new GitRemote(mGit)); |
| 584 | const auto currentBranch = mGit->getCurrentBranch(); |
| 585 | |
| 586 | emit signalMergeRequired(currentBranch, fromBranch); |
| 587 | } |
| 588 | |
| 589 | void CommitHistoryContextMenu::() |
| 590 | { |
| 591 | const auto action = qobject_cast<QAction *>(sender()); |
| 592 | const auto fromBranch = action->data().toString(); |
| 593 | |
| 594 | QScopedPointer<GitRemote> git(new GitRemote(mGit)); |
| 595 | const auto currentBranch = mGit->getCurrentBranch(); |
| 596 | |
| 597 | emit mergeSqushRequested(currentBranch, fromBranch); |
| 598 | } |
| 599 | |
| 600 | void CommitHistoryContextMenu::(const QString &sha) |
| 601 | { |
| 602 | auto remoteBranches = mCache->getReferences(sha, References::Type::RemoteBranches); |
| 603 | const auto localBranches = mCache->getReferences(sha, References::Type::LocalBranch); |
| 604 | |
| 605 | QMap<QString, bool> branchTracking; |
| 606 | |
| 607 | if (remoteBranches.isEmpty()) |
| 608 | { |
| 609 | for (const auto &branch : localBranches) |
| 610 | branchTracking.insert(branch, true); |
| 611 | } |
| 612 | else |
| 613 | { |
| 614 | for (const auto &local : localBranches) |
| 615 | { |
| 616 | const auto iter = std::find_if(remoteBranches.begin(), remoteBranches.end(), [local](const QString &remote) { |
| 617 | if (remote.contains(local)) |
| 618 | return true; |
| 619 | return false; |
| 620 | }); |
| 621 | |
| 622 | branchTracking.insert(local, true); |
| 623 | |
| 624 | if (iter != remoteBranches.end()) |
| 625 | remoteBranches.erase(iter); |
| 626 | } |
| 627 | for (const auto &remote : remoteBranches) |
| 628 | branchTracking.insert(remote, false); |
| 629 | } |
| 630 | |
| 631 | QList<QAction *> branchesToCheckout; |
| 632 | const auto currentBranch = mGit->getCurrentBranch(); |
| 633 | |
| 634 | for (const auto &pair : branchTracking.toStdMap()) |
| 635 | { |
| 636 | if (!branchTracking.isEmpty() && pair.first != currentBranch |
| 637 | && pair.first != QString("origin/%1" ).arg(currentBranch)) |
| 638 | { |
| 639 | const auto checkoutCommitAction = new QAction(QString(tr("%1" )).arg(pair.first)); |
| 640 | checkoutCommitAction->setData(pair.second); |
| 641 | connect(checkoutCommitAction, &QAction::triggered, this, &CommitHistoryContextMenu::checkoutBranch); |
| 642 | branchesToCheckout.append(checkoutCommitAction); |
| 643 | } |
| 644 | } |
| 645 | |
| 646 | const auto = !branchesToCheckout.isEmpty() ? addMenu(tr("Checkout branch" )) : this; |
| 647 | const auto newBranchAction |
| 648 | = branchMenu->addAction(!branchesToCheckout.isEmpty() ? tr("New Branch" ) : tr("Checkout new branch" )); |
| 649 | connect(newBranchAction, &QAction::triggered, this, &CommitHistoryContextMenu::createCheckoutBranch); |
| 650 | |
| 651 | if (!branchesToCheckout.isEmpty()) |
| 652 | { |
| 653 | branchMenu->addSeparator(); |
| 654 | branchMenu->addActions(branchesToCheckout); |
| 655 | } |
| 656 | |
| 657 | if (!mCache->isCommitInCurrentGeneologyTree(sha)) |
| 658 | { |
| 659 | for (const auto &pair : branchTracking.toStdMap()) |
| 660 | { |
| 661 | if (!pair.first.isEmpty() && pair.first != currentBranch |
| 662 | && pair.first != QString("origin/%1" ).arg(currentBranch)) |
| 663 | { |
| 664 | // If is the last commit of a branch |
| 665 | const auto mergeBranchAction = addAction(QString(tr("Merge %1" )).arg(pair.first)); |
| 666 | mergeBranchAction->setData(pair.first); |
| 667 | connect(mergeBranchAction, &QAction::triggered, this, &CommitHistoryContextMenu::merge); |
| 668 | |
| 669 | const auto mergeSquashBranchAction = addAction(QString(tr("Squash-merge %1" )).arg(pair.first)); |
| 670 | mergeSquashBranchAction->setData(pair.first); |
| 671 | connect(mergeSquashBranchAction, &QAction::triggered, this, &CommitHistoryContextMenu::mergeSquash); |
| 672 | } |
| 673 | } |
| 674 | |
| 675 | addSeparator(); |
| 676 | |
| 677 | const auto cherryPickAction = addAction(tr("Cherry pick commit" )); |
| 678 | connect(cherryPickAction, &QAction::triggered, this, &CommitHistoryContextMenu::cherryPickCommit); |
| 679 | } |
| 680 | else |
| 681 | addSeparator(); |
| 682 | } |
| 683 | |
| 684 | void CommitHistoryContextMenu::() |
| 685 | { |
| 686 | if (mCache->pendingLocalChanges()) |
| 687 | { |
| 688 | QMessageBox::warning(this, tr("Squash not possible" ), |
| 689 | tr("Please, make sure there are no pending changes to be commited." )); |
| 690 | } |
| 691 | else |
| 692 | { |
| 693 | const auto squash = new SquashDlg(mGit, mCache, mShas, this); |
| 694 | connect(squash, &SquashDlg::changesCommitted, this, &CommitHistoryContextMenu::fullReload); |
| 695 | squash->exec(); |
| 696 | } |
| 697 | } |
| 698 | |