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
34using namespace QLogger;
35
36CommitHistoryContextMenu::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
55void CommitHistoryContextMenu::createIndividualShaMenu()
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 stashMenu = 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 createMenu = 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 applyMenu = 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 resetMenu = 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 copyMenu = 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 gitServerMenu = 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 checksMenu = 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
200void CommitHistoryContextMenu::createMultipleShasMenu()
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
237void CommitHistoryContextMenu::stashPush()
238{
239 QScopedPointer<GitStashes> git(new GitStashes(mGit));
240 const auto ret = git->stash();
241
242 if (ret.success)
243 emit logReload();
244}
245
246void CommitHistoryContextMenu::stashPop()
247{
248 QScopedPointer<GitStashes> git(new GitStashes(mGit));
249 const auto ret = git->pop();
250
251 if (ret.success)
252 emit logReload();
253}
254
255void CommitHistoryContextMenu::createBranch()
256{
257 BranchDlg dlg({ mShas.first(), BranchDlgMode::CREATE_FROM_COMMIT, mCache, mGit });
258 dlg.exec();
259}
260
261void CommitHistoryContextMenu::createTag()
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
270void CommitHistoryContextMenu::exportAsPatch()
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
300void CommitHistoryContextMenu::checkoutBranch()
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
343void CommitHistoryContextMenu::createCheckoutBranch()
344{
345 BranchDlg dlg({ mShas.constFirst(), BranchDlgMode::CREATE_CHECKOUT_FROM_COMMIT, mCache, mGit });
346 dlg.exec();
347}
348
349void CommitHistoryContextMenu::checkoutCommit()
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
371void CommitHistoryContextMenu::cherryPickCommit()
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
422void CommitHistoryContextMenu::applyPatch()
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
431void CommitHistoryContextMenu::applyCommit()
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
440void CommitHistoryContextMenu::push()
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
487void CommitHistoryContextMenu::pull()
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
518void CommitHistoryContextMenu::fetch()
519{
520 QScopedPointer<GitRemote> git(new GitRemote(mGit));
521
522 if (git->fetch())
523 {
524 mGitTags->getRemoteTags();
525 emit fullReload();
526 }
527}
528
529void CommitHistoryContextMenu::resetSoft()
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
543void CommitHistoryContextMenu::resetMixed()
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
557void CommitHistoryContextMenu::resetHard()
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
578void CommitHistoryContextMenu::merge()
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
589void CommitHistoryContextMenu::mergeSquash()
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
600void CommitHistoryContextMenu::addBranchActions(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 branchMenu = !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
684void CommitHistoryContextMenu::showSquashDialog()
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