1#include "BranchesWidget.h"
2
3#include <AddSubtreeDlg.h>
4#include <BranchTreeWidget.h>
5#include <BranchesViewDelegate.h>
6#include <BranchesWidgetMinimal.h>
7#include <ClickableFrame.h>
8#include <GitBase.h>
9#include <GitCache.h>
10#include <GitConfig.h>
11#include <GitQlientBranchItemRole.h>
12#include <GitQlientSettings.h>
13#include <GitStashes.h>
14#include <GitSubmodules.h>
15#include <GitSubtree.h>
16#include <GitTags.h>
17#include <StashesContextMenu.h>
18#include <SubmodulesContextMenu.h>
19
20#include <QApplication>
21#include <QHeaderView>
22#include <QLabel>
23#include <QLineEdit>
24#include <QListWidget>
25#include <QMenu>
26#include <QMessageBox>
27#include <QPushButton>
28#include <QToolButton>
29#include <QVBoxLayout>
30
31#include <QLogger.h>
32
33using namespace QLogger;
34using namespace GitQlient;
35
36namespace
37{
38QTreeWidgetItem *getChild(QTreeWidgetItem *parent, const QString &childName)
39{
40 QTreeWidgetItem *child = nullptr;
41
42 if (parent)
43 {
44 const auto childrenCount = parent->childCount();
45
46 for (auto i = 0; i < childrenCount; ++i)
47 if (parent->child(i)->text(0) == childName)
48 child = parent->child(i);
49 }
50
51 return child;
52}
53}
54
55BranchesWidget::BranchesWidget(const QSharedPointer<GitCache> &cache, const QSharedPointer<GitBase> &git,
56 QWidget *parent)
57 : QFrame(parent)
58 , mCache(cache)
59 , mGit(git)
60 , mGitTags(new GitTags(mGit, mCache))
61 , mLocalBranchesTree(new BranchTreeWidget(mCache, mGit))
62 , mRemoteBranchesTree(new BranchTreeWidget(mCache, mGit))
63 , mTagsTree(new RefTreeWidget())
64 , mStashesList(new QListWidget())
65 , mStashesCount(new QLabel(tr("(0)")))
66 , mStashesArrow(new QLabel())
67 , mSubmodulesCount(new QLabel("(0)"))
68 , mSubmodulesArrow(new QLabel())
69 , mSubmodulesList(new QListWidget())
70 , mSubtreeCount(new QLabel("(0)"))
71 , mSubtreeArrow(new QLabel())
72 , mSubtreeList(new QListWidget())
73 , mMinimize(new QPushButton())
74 , mMinimal(new BranchesWidgetMinimal(mCache, mGit))
75{
76 connect(mCache.get(), &GitCache::signalCacheUpdated, this, &BranchesWidget::showBranches);
77 connect(mCache.get(), &GitCache::signalCacheUpdated, this, &BranchesWidget::processTags);
78
79 setAttribute(Qt::WA_DeleteOnClose);
80
81 mLocalBranchesTree->setLocalRepo(true);
82 mLocalBranchesTree->setMouseTracking(true);
83 mLocalBranchesTree->setItemDelegate(mLocalDelegate = new BranchesViewDelegate());
84 mLocalBranchesTree->setColumnCount(1);
85 mLocalBranchesTree->setObjectName("LocalBranches");
86
87 const auto localHeader = mLocalBranchesTree->headerItem();
88 localHeader->setText(0, tr("Local"));
89
90 mRemoteBranchesTree->setColumnCount(1);
91 mRemoteBranchesTree->setMouseTracking(true);
92 mRemoteBranchesTree->setItemDelegate(mRemotesDelegate = new BranchesViewDelegate());
93
94 const auto remoteHeader = mRemoteBranchesTree->headerItem();
95 remoteHeader->setText(0, tr("Remote"));
96
97 const auto tagHeader = mTagsTree->headerItem();
98 tagHeader->setText(0, tr("Tags"));
99
100 mTagsTree->setColumnCount(1);
101 mTagsTree->setMouseTracking(true);
102 mTagsTree->setItemDelegate(mTagsDelegate = new BranchesViewDelegate(true));
103 mTagsTree->setContextMenuPolicy(Qt::CustomContextMenu);
104
105 GitQlientSettings settings(mGit->getGitDir());
106
107 /* STASHES START */
108 if (const auto visible = settings.localValue("StashesHeader", true).toBool(); !visible)
109 {
110 const auto icon = QIcon(!visible ? QString(":/icons/add") : QString(":/icons/remove"));
111 mStashesArrow->setPixmap(icon.pixmap(QSize(15, 15)));
112 mStashesList->setVisible(visible);
113 }
114 else
115 mStashesArrow->setPixmap(QIcon(":/icons/remove").pixmap(QSize(15, 15)));
116
117 const auto stashHeaderFrame = new ClickableFrame();
118 const auto stashHeaderLayout = new QHBoxLayout(stashHeaderFrame);
119 stashHeaderLayout->setContentsMargins(10, 0, 0, 0);
120 stashHeaderLayout->setSpacing(10);
121 stashHeaderLayout->addWidget(new QLabel(tr("Stashes")));
122 stashHeaderLayout->addWidget(mStashesCount);
123 stashHeaderLayout->addStretch();
124 stashHeaderLayout->addWidget(mStashesArrow);
125
126 mStashesList->setMouseTracking(true);
127 mStashesList->setContextMenuPolicy(Qt::CustomContextMenu);
128
129 const auto stashLayout = new QVBoxLayout();
130 stashLayout->setContentsMargins(QMargins());
131 stashLayout->setSpacing(0);
132 stashLayout->addWidget(stashHeaderFrame);
133 stashLayout->addSpacing(5);
134 stashLayout->addWidget(mStashesList);
135
136 const auto stashFrame = new QFrame();
137 stashFrame->setObjectName("sectionFrame");
138 stashFrame->setLayout(stashLayout);
139
140 /* STASHES END */
141
142 /* SUBMODULES START */
143 if (const auto visible = settings.localValue("SubmodulesHeader", true).toBool(); !visible)
144 {
145 const auto icon = QIcon(!visible ? QString(":/icons/add") : QString(":/icons/remove"));
146 mSubmodulesArrow->setPixmap(icon.pixmap(QSize(15, 15)));
147 mSubmodulesList->setVisible(visible);
148 }
149 else
150 mSubmodulesArrow->setPixmap(QIcon(":/icons/remove").pixmap(QSize(15, 15)));
151
152 const auto submoduleHeaderFrame = new ClickableFrame();
153 const auto submoduleHeaderLayout = new QHBoxLayout(submoduleHeaderFrame);
154 submoduleHeaderLayout->setContentsMargins(10, 0, 0, 0);
155 submoduleHeaderLayout->setSpacing(10);
156 submoduleHeaderLayout->addWidget(new QLabel(tr("Submodules")));
157 submoduleHeaderLayout->addWidget(mSubmodulesCount);
158 submoduleHeaderLayout->addStretch();
159 submoduleHeaderLayout->addWidget(mSubmodulesArrow);
160
161 mSubmodulesList->setMouseTracking(true);
162 mSubmodulesList->setContextMenuPolicy(Qt::CustomContextMenu);
163 connect(mSubmodulesList, &QListWidget::itemDoubleClicked, this, [this](QListWidgetItem *item) {
164 emit signalOpenSubmodule(mGit->getWorkingDir().append("/").append(item->text()));
165 });
166
167 const auto submoduleLayout = new QVBoxLayout();
168 submoduleLayout->setContentsMargins(QMargins());
169 submoduleLayout->setSpacing(0);
170 submoduleLayout->addWidget(submoduleHeaderFrame);
171 submoduleLayout->addSpacing(5);
172 submoduleLayout->addWidget(mSubmodulesList);
173
174 const auto submoduleFrame = new QFrame();
175 submoduleFrame->setObjectName("sectionFrame");
176 submoduleFrame->setLayout(submoduleLayout);
177
178 /* SUBMODULES END */
179
180 /* SUBTREE START */
181 if (const auto visible = settings.localValue("SubtreeHeader", true).toBool(); !visible)
182 {
183 const auto icon = QIcon(!visible ? QString(":/icons/add") : QString(":/icons/remove"));
184 mSubtreeArrow->setPixmap(icon.pixmap(QSize(15, 15)));
185 mSubtreeList->setVisible(visible);
186 }
187 else
188 mSubtreeArrow->setPixmap(QIcon(":/icons/remove").pixmap(QSize(15, 15)));
189
190 const auto subtreeHeaderFrame = new ClickableFrame();
191 const auto subtreeHeaderLayout = new QHBoxLayout(subtreeHeaderFrame);
192 subtreeHeaderLayout->setContentsMargins(10, 0, 0, 0);
193 subtreeHeaderLayout->setSpacing(10);
194 subtreeHeaderLayout->addWidget(new QLabel(tr("Subtrees")));
195 subtreeHeaderLayout->addWidget(mSubtreeCount);
196 subtreeHeaderLayout->addStretch();
197 subtreeHeaderLayout->addWidget(mSubtreeArrow);
198
199 mSubtreeList->setMouseTracking(true);
200 mSubtreeList->setContextMenuPolicy(Qt::CustomContextMenu);
201
202 const auto subtreeLayout = new QVBoxLayout();
203 subtreeLayout->setContentsMargins(QMargins());
204 subtreeLayout->setSpacing(0);
205 subtreeLayout->addWidget(subtreeHeaderFrame);
206 subtreeLayout->addSpacing(5);
207 subtreeLayout->addWidget(mSubtreeList);
208
209 const auto subtreeFrame = new QFrame();
210 subtreeFrame->setObjectName("sectionFrame");
211 subtreeFrame->setLayout(subtreeLayout);
212
213 /* SUBTREE END */
214
215 const auto searchBranch = new QLineEdit();
216 searchBranch->setPlaceholderText(tr("Prese ENTER to search a branch or tag..."));
217 searchBranch->setObjectName("SearchInput");
218 connect(searchBranch, &QLineEdit::returnPressed, this, &BranchesWidget::onSearchBranch);
219
220 mMinimize->setIcon(QIcon(":/icons/ahead"));
221 mMinimize->setToolTip(tr("Show minimalist view"));
222 mMinimize->setObjectName("BranchesWidgetOptionsButton");
223 connect(mMinimize, &QPushButton::clicked, this, &BranchesWidget::minimalView);
224
225 const auto mainControlsLayout = new QHBoxLayout();
226 mainControlsLayout->setContentsMargins(QMargins());
227 mainControlsLayout->setSpacing(5);
228 mainControlsLayout->addWidget(mMinimize);
229 mainControlsLayout->addWidget(searchBranch);
230
231 const auto separator1 = new QFrame();
232 separator1->setObjectName("separator");
233
234 const auto separator2 = new QFrame();
235 separator2->setObjectName("separator");
236
237 const auto panelsLayout = new QVBoxLayout();
238 panelsLayout->setContentsMargins(QMargins());
239 panelsLayout->setSpacing(0);
240 panelsLayout->addWidget(mLocalBranchesTree);
241 panelsLayout->addWidget(separator1);
242 panelsLayout->addWidget(mRemoteBranchesTree);
243 panelsLayout->addWidget(separator2);
244 panelsLayout->addWidget(mTagsTree);
245 panelsLayout->addWidget(stashFrame);
246 panelsLayout->addWidget(submoduleFrame);
247 panelsLayout->addWidget(subtreeFrame);
248
249 const auto panelsFrame = new QFrame();
250 panelsFrame->setObjectName("panelsFrame");
251 panelsFrame->setLayout(panelsLayout);
252
253 const auto vLayout = new QVBoxLayout();
254 vLayout->setContentsMargins(0, 0, 0, 0);
255 vLayout->setSpacing(0);
256 vLayout->addLayout(mainControlsLayout);
257 vLayout->addSpacing(5);
258 vLayout->addWidget(panelsFrame);
259
260 mFullBranchFrame = new QFrame();
261
262 const auto mainBranchLayout = new QHBoxLayout(mFullBranchFrame);
263 mainBranchLayout->setContentsMargins(QMargins());
264 mainBranchLayout->setSpacing(0);
265 mainBranchLayout->addLayout(vLayout);
266
267 const auto mainLayout = new QGridLayout(this);
268 mainLayout->setContentsMargins(QMargins());
269 mainLayout->setSpacing(0);
270 mainLayout->addWidget(mFullBranchFrame, 0, 0, 3, 1);
271 mainLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::Expanding), 0, 1);
272 mainLayout->addWidget(mMinimal, 1, 1);
273 mainLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::Expanding), 2, 1);
274
275 const auto isMinimalVisible = settings.localValue("MinimalBranchesView", false).toBool();
276 mFullBranchFrame->setVisible(!isMinimalVisible);
277 mMinimal->setVisible(isMinimalVisible);
278 connect(mMinimal, &BranchesWidgetMinimal::showFullBranchesView, this, &BranchesWidget::fullView);
279 connect(mMinimal, &BranchesWidgetMinimal::commitSelected, this, &BranchesWidget::signalSelectCommit);
280 connect(mMinimal, &BranchesWidgetMinimal::stashSelected, this, &BranchesWidget::onStashSelected);
281
282 /*
283 connect(mLocalBranchesTree, &BranchTreeWidget::signalRefreshPRsCache, mCache.get(),
284 &GitCache::refreshPRsCache);
285*/
286 connect(mLocalBranchesTree, &BranchTreeWidget::signalSelectCommit, this, &BranchesWidget::signalSelectCommit);
287 connect(mLocalBranchesTree, &BranchTreeWidget::signalSelectCommit, mRemoteBranchesTree,
288 &BranchTreeWidget::clearSelection);
289 connect(mLocalBranchesTree, &BranchTreeWidget::signalFetchPerformed, mGitTags.data(), &GitTags::getRemoteTags);
290 connect(mLocalBranchesTree, &BranchTreeWidget::fullReload, this, &BranchesWidget::fullReload);
291 connect(mLocalBranchesTree, &BranchTreeWidget::logReload, this, &BranchesWidget::logReload);
292 connect(mLocalBranchesTree, &BranchTreeWidget::signalMergeRequired, this, &BranchesWidget::signalMergeRequired);
293 connect(mLocalBranchesTree, &BranchTreeWidget::mergeSqushRequested, this, &BranchesWidget::mergeSqushRequested);
294 connect(mLocalBranchesTree, &BranchTreeWidget::signalPullConflict, this, &BranchesWidget::signalPullConflict);
295
296 connect(mRemoteBranchesTree, &BranchTreeWidget::signalSelectCommit, this, &BranchesWidget::signalSelectCommit);
297 connect(mRemoteBranchesTree, &BranchTreeWidget::signalSelectCommit, mLocalBranchesTree,
298 &BranchTreeWidget::clearSelection);
299 connect(mRemoteBranchesTree, &BranchTreeWidget::signalFetchPerformed, mGitTags.data(), &GitTags::getRemoteTags);
300 connect(mRemoteBranchesTree, &BranchTreeWidget::fullReload, this, &BranchesWidget::fullReload);
301 connect(mRemoteBranchesTree, &BranchTreeWidget::logReload, this, &BranchesWidget::logReload);
302 connect(mRemoteBranchesTree, &BranchTreeWidget::signalMergeRequired, this, &BranchesWidget::signalMergeRequired);
303 connect(mRemoteBranchesTree, &BranchTreeWidget::mergeSqushRequested, this, &BranchesWidget::mergeSqushRequested);
304
305 connect(mTagsTree, &QTreeWidget::itemClicked, this, &BranchesWidget::onTagClicked);
306 connect(mTagsTree, &QListWidget::customContextMenuRequested, this, &BranchesWidget::showTagsContextMenu);
307 connect(mStashesList, &QListWidget::itemClicked, this, &BranchesWidget::onStashClicked);
308 connect(mStashesList, &QListWidget::customContextMenuRequested, this, &BranchesWidget::showStashesContextMenu);
309 connect(mSubmodulesList, &QListWidget::customContextMenuRequested, this, &BranchesWidget::showSubmodulesContextMenu);
310 connect(mSubtreeList, &QListWidget::customContextMenuRequested, this, &BranchesWidget::showSubtreesContextMenu);
311 connect(stashHeaderFrame, &ClickableFrame::clicked, this, &BranchesWidget::onStashesHeaderClicked);
312 connect(submoduleHeaderFrame, &ClickableFrame::clicked, this, &BranchesWidget::onSubmodulesHeaderClicked);
313 connect(subtreeHeaderFrame, &ClickableFrame::clicked, this, &BranchesWidget::onSubtreesHeaderClicked);
314}
315
316BranchesWidget::~BranchesWidget()
317{
318 delete mLocalDelegate;
319 delete mRemotesDelegate;
320 delete mTagsDelegate;
321}
322
323bool BranchesWidget::isMinimalViewActive() const
324{
325 GitQlientSettings settings(mGit->getGitDir());
326 return settings.localValue("MinimalBranchesView", false).toBool();
327}
328
329void BranchesWidget::showBranches()
330{
331 QLog_Info("UI", QString("Loading branches data"));
332
333 clear();
334 mMinimal->clearActions();
335
336 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
337
338 auto branches = mCache->getBranches(References::Type::LocalBranch);
339
340 if (!branches.empty())
341 {
342 QLog_Info("UI", QString("Fetched {%1} local branches").arg(branches.count()));
343 QLog_Info("UI", QString("Processing local branches..."));
344
345 for (const auto &pair : qAsConst(branches))
346 {
347 for (const auto &branch : pair.second)
348 {
349 if (!branch.contains("HEAD->"))
350 {
351 processLocalBranch(pair.first, branch);
352 mMinimal->configureLocalMenu(pair.first, branch);
353 }
354 }
355 }
356
357 QLog_Info("UI", QString("... local branches processed"));
358 }
359
360 branches.clear();
361 branches.squeeze();
362 branches = mCache->getBranches(References::Type::RemoteBranches);
363
364 if (!branches.empty())
365 {
366 QLog_Info("UI", QString("Fetched {%1} remote branches").arg(branches.count()));
367 QLog_Info("UI", QString("Processing remote branches..."));
368
369 for (const auto &pair : qAsConst(branches))
370 {
371 for (const auto &branch : pair.second)
372 {
373 if (!branch.contains("HEAD->"))
374 {
375 processRemoteBranch(pair.first, branch);
376 mMinimal->configureRemoteMenu(pair.first, branch);
377 }
378 }
379 }
380
381 branches.clear();
382 branches.squeeze();
383
384 QLog_Info("UI", QString("... remote branches processed"));
385 }
386
387 processStashes();
388 processSubmodules();
389 processSubtrees();
390
391 QApplication::restoreOverrideCursor();
392
393 adjustBranchesTree(mLocalBranchesTree);
394}
395
396void BranchesWidget::refreshCurrentBranchLink()
397{
398 mLocalBranchesTree->reloadCurrentBranchLink();
399}
400
401void BranchesWidget::clear()
402{
403 blockSignals(true);
404 mLocalBranchesTree->clear();
405 mRemoteBranchesTree->clear();
406 blockSignals(false);
407}
408
409void BranchesWidget::fullView()
410{
411 mFullBranchFrame->setVisible(true);
412 mMinimal->setVisible(false);
413
414 emit minimalViewStateChanged(false);
415
416 GitQlientSettings settings(mGit->getGitDir());
417 settings.setLocalValue("MinimalBranchesView", mMinimal->isVisible());
418}
419
420void BranchesWidget::returnToSavedView()
421{
422 GitQlientSettings settings(mGit->getGitDir());
423 const auto savedState = settings.localValue("MinimalBranchesView", false).toBool();
424
425 if (savedState != mMinimal->isVisible())
426 {
427 mFullBranchFrame->setVisible(!savedState);
428 mMinimal->setVisible(savedState);
429
430 emit minimalViewStateChanged(savedState);
431 }
432}
433
434void BranchesWidget::minimalView()
435{
436 forceMinimalView();
437
438 GitQlientSettings settings(mGit->getGitDir());
439 settings.setLocalValue("MinimalBranchesView", mMinimal->isVisible());
440}
441
442void BranchesWidget::forceMinimalView()
443{
444 mFullBranchFrame->setVisible(false);
445 mMinimal->setVisible(true);
446
447 emit minimalViewStateChanged(true);
448}
449
450void BranchesWidget::onPanelsVisibilityChaned()
451{
452 GitQlientSettings settings(mGit->getGitDir());
453
454 auto visible = settings.localValue("StashesHeader", true).toBool();
455 auto icon = QIcon(!visible ? QString(":/icons/add") : QString(":/icons/remove"));
456 mStashesArrow->setPixmap(icon.pixmap(QSize(15, 15)));
457 mStashesList->setVisible(visible);
458
459 visible = settings.localValue("SubmodulesHeader", true).toBool();
460 icon = QIcon(!visible ? QString(":/icons/add") : QString(":/icons/remove"));
461 mSubmodulesArrow->setPixmap(icon.pixmap(QSize(15, 15)));
462 mSubmodulesList->setVisible(visible);
463
464 visible = settings.localValue("SubtreeHeader", true).toBool();
465 icon = QIcon(!visible ? QString(":/icons/add") : QString(":/icons/remove"));
466 mSubtreeArrow->setPixmap(icon.pixmap(QSize(15, 15)));
467 mSubtreeList->setVisible(visible);
468}
469
470void BranchesWidget::processLocalBranch(const QString &sha, QString branch)
471{
472 QLog_Debug("UI", QString("Adding local branch {%1}").arg(branch));
473
474 auto isCurrentBranch = false;
475
476 if (branch == mGit->getCurrentBranch())
477 isCurrentBranch = true;
478
479 const auto fullBranchName = branch;
480
481 QVector<QTreeWidgetItem *> parents;
482 QTreeWidgetItem *parent = nullptr;
483 auto folders = branch.split("/");
484 branch = folders.takeLast();
485
486 for (const auto &folder : qAsConst(folders))
487 {
488 QTreeWidgetItem *child = nullptr;
489
490 if (parent)
491 {
492 child = getChild(parent, folder);
493 parents.append(child);
494 }
495 else
496 {
497 for (auto i = 0; i < mLocalBranchesTree->topLevelItemCount(); ++i)
498 {
499 if (mLocalBranchesTree->topLevelItem(i)->text(0) == folder)
500 {
501 child = mLocalBranchesTree->topLevelItem(i);
502 parents.append(child);
503 }
504 }
505 }
506
507 if (!child)
508 {
509 const auto item = parent ? new QTreeWidgetItem(parent) : new QTreeWidgetItem();
510 item->setText(0, folder);
511
512 if (!parent)
513 mLocalBranchesTree->addTopLevelItem(item);
514
515 parent = item;
516 parents.append(parent);
517 }
518 else
519 {
520 parent = child;
521 parents.append(child);
522 }
523 }
524
525 auto item = new QTreeWidgetItem(parent);
526 item->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicator);
527 item->setText(0, branch);
528 item->setData(0, GitQlient::IsCurrentBranchRole, isCurrentBranch);
529 item->setData(0, GitQlient::FullNameRole, fullBranchName);
530 item->setData(0, GitQlient::LocalBranchRole, true);
531 item->setData(0, GitQlient::ShaRole, sha);
532 item->setData(0, Qt::ToolTipRole, fullBranchName);
533 item->setData(0, GitQlient::IsLeaf, true);
534
535 if (isCurrentBranch)
536 {
537 item->setSelected(true);
538
539 for (const auto parent : parents)
540 {
541 mLocalBranchesTree->setCurrentItem(item);
542 mLocalBranchesTree->expandItem(parent);
543 const auto indexToScroll = mLocalBranchesTree->currentIndex();
544 mLocalBranchesTree->scrollTo(indexToScroll);
545 }
546 }
547
548 parents.clear();
549 parents.squeeze();
550
551 mLocalBranchesTree->addTopLevelItem(item);
552
553 QLog_Debug("UI", QString("Finish gathering local branch information"));
554}
555
556void BranchesWidget::processRemoteBranch(const QString &sha, QString branch)
557{
558 const auto fullBranchName = branch;
559 auto folders = branch.split("/");
560 branch = folders.takeLast();
561
562 QTreeWidgetItem *parent = nullptr;
563
564 for (const auto &folder : qAsConst(folders))
565 {
566 QTreeWidgetItem *child = nullptr;
567
568 if (parent)
569 child = getChild(parent, folder);
570 else
571 {
572 for (auto i = 0; i < mRemoteBranchesTree->topLevelItemCount(); ++i)
573 {
574 if (mRemoteBranchesTree->topLevelItem(i)->text(0) == folder)
575 child = mRemoteBranchesTree->topLevelItem(i);
576 }
577 }
578
579 if (!child)
580 {
581 const auto item = parent ? new QTreeWidgetItem(parent) : new QTreeWidgetItem();
582 item->setText(0, folder);
583
584 if (!parent)
585 {
586 item->setData(0, GitQlient::IsRoot, true);
587 mRemoteBranchesTree->addTopLevelItem(item);
588 }
589
590 parent = item;
591 }
592 else
593 parent = child;
594 }
595
596 QLog_Trace("UI", QString("Adding remote branch {%1}").arg(branch));
597
598 const auto item = new QTreeWidgetItem(parent);
599 item->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicator);
600 item->setText(0, branch);
601 item->setData(0, GitQlient::FullNameRole, fullBranchName);
602 item->setData(0, GitQlient::LocalBranchRole, false);
603 item->setData(0, GitQlient::ShaRole, sha);
604 item->setData(0, Qt::ToolTipRole, fullBranchName);
605 item->setData(0, GitQlient::IsLeaf, true);
606}
607
608void BranchesWidget::processTags()
609{
610 mTagsTree->clear();
611
612 const auto localTags = mCache->getTags(References::Type::LocalTag);
613 auto remoteTags = mCache->getTags(References::Type::RemoteTag);
614
615 for (auto iter = localTags.cbegin(); iter != localTags.cend(); ++iter)
616 {
617 QTreeWidgetItem *parent = nullptr;
618 auto fullTagName = iter.key();
619 auto folders = fullTagName.split("/");
620 auto tagName = folders.takeLast();
621
622 for (const auto &folder : qAsConst(folders))
623 {
624 QTreeWidgetItem *child = nullptr;
625
626 if (parent)
627 child = getChild(parent, folder);
628 else
629 {
630 for (auto i = 0; i < mTagsTree->topLevelItemCount(); ++i)
631 {
632 if (mTagsTree->topLevelItem(i)->text(0) == folder)
633 child = mTagsTree->topLevelItem(i);
634 }
635 }
636
637 if (!child)
638 {
639 const auto item = parent ? new QTreeWidgetItem(parent) : new QTreeWidgetItem();
640 item->setText(0, folder);
641
642 if (!parent)
643 mTagsTree->addTopLevelItem(item);
644
645 parent = item;
646 }
647 else
648 parent = child;
649 }
650
651 const auto item = new QTreeWidgetItem(parent);
652
653 if (!remoteTags.contains(fullTagName))
654 {
655 tagName += " (local)";
656 item->setData(0, LocalBranchRole, false);
657 }
658 else
659 {
660 item->setData(0, LocalBranchRole, true);
661 remoteTags.remove(fullTagName);
662 }
663
664 QLog_Trace("UI", QString("Adding tag {%1}").arg(tagName));
665
666 item->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicator);
667 item->setText(0, tagName);
668 item->setData(0, GitQlient::FullNameRole, fullTagName);
669 item->setData(0, GitQlient::ShaRole, iter.value());
670 item->setData(0, Qt::ToolTipRole, fullTagName);
671 item->setData(0, GitQlient::IsLeaf, true);
672
673 mTagsTree->addTopLevelItem(item);
674 }
675
676 for (auto iter = remoteTags.cbegin(); iter != remoteTags.cend(); ++iter)
677 {
678 QTreeWidgetItem *parent = nullptr;
679 auto fullTagName = iter.key();
680 auto folders = fullTagName.split("/");
681 auto tagName = folders.takeLast();
682
683 for (const auto &folder : qAsConst(folders))
684 {
685 QTreeWidgetItem *child = nullptr;
686
687 if (parent)
688 child = getChild(parent, folder);
689 else
690 {
691 for (auto i = 0; i < mTagsTree->topLevelItemCount(); ++i)
692 {
693 if (mTagsTree->topLevelItem(i)->text(0) == folder)
694 child = mTagsTree->topLevelItem(i);
695 }
696 }
697
698 if (!child)
699 {
700 const auto item = parent ? new QTreeWidgetItem(parent) : new QTreeWidgetItem();
701 item->setText(0, folder);
702
703 if (!parent)
704 mTagsTree->addTopLevelItem(item);
705
706 parent = item;
707 }
708 else
709 parent = child;
710 }
711
712 QLog_Trace("UI", QString("Adding tag {%1}").arg(tagName));
713
714 const auto item = new QTreeWidgetItem(parent);
715 item->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicator);
716 item->setText(0, tagName);
717 item->setData(0, GitQlient::FullNameRole, fullTagName);
718 item->setData(0, GitQlient::ShaRole, iter.value());
719 item->setData(0, Qt::ToolTipRole, fullTagName);
720 item->setData(0, GitQlient::IsLeaf, true);
721
722 mTagsTree->addTopLevelItem(item);
723 }
724
725 mTagsTree->update();
726}
727
728void BranchesWidget::processStashes()
729{
730 mStashesList->clear();
731
732 QScopedPointer<GitStashes> git(new GitStashes(mGit));
733 const auto stashes = git->getStashes();
734
735 QLog_Info("UI", QString("Fetching {%1} stashes").arg(stashes.count()));
736
737 for (const auto &stash : stashes)
738 {
739 const auto stashId = stash.split(":").first();
740 const auto stashDesc = stash.split("}: ").last();
741 const auto item = new QListWidgetItem(stashDesc);
742 item->setData(Qt::UserRole, stashId);
743 mStashesList->addItem(item);
744 mMinimal->configureStashesMenu(stashId, stashDesc);
745 }
746
747 mStashesCount->setText(QString("(%1)").arg(stashes.count()));
748}
749
750void BranchesWidget::processSubmodules()
751{
752 mSubmodulesList->clear();
753
754 QScopedPointer<GitSubmodules> git(new GitSubmodules(mGit));
755 const auto submodules = git->getSubmodules();
756
757 QLog_Info("UI", QString("Fetching {%1} submodules").arg(submodules.count()));
758
759 for (const auto &submodule : submodules)
760 {
761 mSubmodulesList->addItem(submodule);
762 mMinimal->configureSubmodulesMenu(submodule);
763 }
764
765 mSubmodulesCount->setText('(' + QString::number(submodules.count()) + ')');
766}
767
768void BranchesWidget::processSubtrees()
769{
770 mSubtreeList->clear();
771
772 QScopedPointer<GitSubtree> git(new GitSubtree(mGit));
773
774 const auto ret = git->list();
775
776 if (ret.success)
777 {
778 const auto rawData = ret.output;
779 const auto commits = rawData.split("\n\n");
780 auto count = 0;
781
782 for (auto &subtreeRawData : commits)
783 {
784 if (!subtreeRawData.isEmpty())
785 {
786 QString name;
787 QString sha;
788 auto fields = subtreeRawData.split("\n");
789
790 for (auto &field : fields)
791 {
792 if (field.contains("git-subtree-dir:"))
793 name = field.remove("git-subtree-dir:").trimmed();
794 else if (field.contains("git-subtree-split"))
795 sha = field.remove("git-subtree-split:").trimmed();
796 }
797
798 mSubtreeList->addItem(name);
799 ++count;
800 }
801 }
802
803 mSubtreeCount->setText('(' + QString::number(count) + ')');
804 }
805}
806
807void BranchesWidget::adjustBranchesTree(BranchTreeWidget *treeWidget)
808{
809 for (auto i = 1; i < treeWidget->columnCount(); ++i)
810 treeWidget->resizeColumnToContents(i);
811
812 treeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch);
813
814 for (auto i = 1; i < treeWidget->columnCount(); ++i)
815 treeWidget->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents);
816
817 treeWidget->header()->setStretchLastSection(false);
818}
819
820void BranchesWidget::showTagsContextMenu(const QPoint &p)
821{
822 const auto item = mTagsTree->itemAt(p);
823
824 if (!item)
825 return;
826
827 const auto tagName = item->data(0, GitQlient::FullNameRole).toString();
828
829 if (!tagName.isEmpty())
830 {
831 const auto isRemote = item->data(0, LocalBranchRole).toBool();
832 const auto menu = new QMenu(this);
833 const auto removeTagAction = menu->addAction(tr("Remove tag"));
834 connect(removeTagAction, &QAction::triggered, this, [this, tagName, isRemote]() {
835 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
836 QScopedPointer<GitTags> git(new GitTags(mGit));
837 const auto ret = git->removeTag(tagName, isRemote);
838 QApplication::restoreOverrideCursor();
839
840 if (ret.success)
841 mGitTags->getRemoteTags();
842 });
843
844 const auto pushTagAction = menu->addAction(tr("Push tag"));
845 pushTagAction->setEnabled(!isRemote);
846 connect(pushTagAction, &QAction::triggered, this, [this, tagName]() {
847 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
848 QScopedPointer<GitTags> git(new GitTags(mGit));
849 const auto ret = git->pushTag(tagName);
850 QApplication::restoreOverrideCursor();
851
852 if (ret.success)
853 mGitTags->getRemoteTags();
854 });
855
856 menu->exec(mTagsTree->viewport()->mapToGlobal(p));
857 }
858}
859
860void BranchesWidget::showStashesContextMenu(const QPoint &p)
861{
862 QLog_Info("UI", QString("Requesting context menu for stashes"));
863
864 const auto index = mStashesList->indexAt(p);
865
866 if (index.isValid())
867 {
868 const auto menu = new StashesContextMenu(mGit, index.data(Qt::UserRole).toString(), this);
869 connect(menu, &StashesContextMenu::signalUpdateView, this, &BranchesWidget::fullReload);
870 connect(menu, &StashesContextMenu::signalContentRemoved, this, &BranchesWidget::fullReload);
871 menu->exec(mStashesList->viewport()->mapToGlobal(p));
872 }
873}
874
875void BranchesWidget::showSubmodulesContextMenu(const QPoint &p)
876{
877 QLog_Info("UI", QString("Requesting context menu for submodules"));
878
879 const auto menu = new SubmodulesContextMenu(mGit, mSubmodulesList->indexAt(p), this);
880 connect(menu, &SubmodulesContextMenu::openSubmodule, this, &BranchesWidget::signalOpenSubmodule);
881 connect(menu, &SubmodulesContextMenu::infoUpdated, this, &BranchesWidget::fullReload);
882
883 menu->exec(mSubmodulesList->viewport()->mapToGlobal(p));
884}
885
886void BranchesWidget::showSubtreesContextMenu(const QPoint &p)
887{
888 QLog_Info("UI", QString("Requesting context menu for subtrees"));
889
890 QModelIndex index = mSubtreeList->indexAt(p);
891
892 const auto menu = new QMenu(this);
893
894 if (index.isValid())
895 {
896 connect(menu->addAction(tr("Pull")), &QAction::triggered, this, [this, index]() {
897 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
898
899 const auto prefix = index.data().toString();
900 const auto subtreeData = getSubtreeData(prefix);
901
902 QScopedPointer<GitSubtree> git(new GitSubtree(mGit));
903 const auto ret = git->pull(subtreeData.first, subtreeData.second, prefix);
904 QApplication::restoreOverrideCursor();
905
906 if (ret.success)
907 emit fullReload();
908 else
909 QMessageBox::warning(this, tr("Error when pulling"), ret.output);
910 });
911 /*
912 connect(menu->addAction(tr("Merge")), &QAction::triggered, this, [this, index]() {
913 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
914
915 const auto subtreeData = getSubtreeData(index.data().toString());
916
917 QScopedPointer<GitSubtree> git(new GitSubtree(mGit));
918 const auto ret = git->pull(subtreeData.first, subtreeData.second);
919 QApplication::restoreOverrideCursor();
920
921 if (ret.success)
922 emit signalBranchesUpdated();
923 });
924*/
925 connect(menu->addAction(tr("Push")), &QAction::triggered, this, [this, index]() {
926 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
927
928 const auto prefix = index.data().toString();
929 const auto subtreeData = getSubtreeData(prefix);
930
931 QScopedPointer<GitSubtree> git(new GitSubtree(mGit));
932 const auto ret = git->push(subtreeData.first, subtreeData.second, prefix);
933 QApplication::restoreOverrideCursor();
934
935 if (ret.success)
936 emit fullReload();
937 else
938 QMessageBox::warning(this, tr("Error when pushing"), ret.output);
939 });
940
941 const auto addSubtree = menu->addAction(tr("Configure"));
942 connect(addSubtree, &QAction::triggered, this, [this, index]() {
943 const auto prefix = index.data().toString();
944 const auto subtreeData = getSubtreeData(prefix);
945 AddSubtreeDlg addDlg(prefix, subtreeData.first, subtreeData.second, mGit);
946 const auto ret = addDlg.exec();
947 if (ret == QDialog::Accepted)
948 emit fullReload();
949 });
950 // menu->addAction(tr("Split"));
951 }
952 else
953 {
954 const auto addSubtree = menu->addAction(tr("Add subtree"));
955 connect(addSubtree, &QAction::triggered, this, [this]() {
956 AddSubtreeDlg addDlg(mGit);
957 const auto ret = addDlg.exec();
958 if (ret == QDialog::Accepted)
959 emit fullReload();
960 });
961 }
962
963 menu->exec(mSubtreeList->viewport()->mapToGlobal(p));
964}
965
966void BranchesWidget::onStashesHeaderClicked()
967{
968 const auto stashesAreVisible = mStashesList->isVisible();
969 const auto icon = QIcon(stashesAreVisible ? QString(":/icons/add") : QString(":/icons/remove"));
970 mStashesArrow->setPixmap(icon.pixmap(QSize(15, 15)));
971 mStashesList->setVisible(!stashesAreVisible);
972
973 GitQlientSettings settings(mGit->getGitDir());
974 settings.setLocalValue("StashesHeader", !stashesAreVisible);
975
976 emit panelsVisibilityChanged();
977}
978
979void BranchesWidget::onSubmodulesHeaderClicked()
980{
981 const auto submodulesAreVisible = mSubmodulesList->isVisible();
982 const auto icon = QIcon(submodulesAreVisible ? QString(":/icons/add") : QString(":/icons/remove"));
983 mSubmodulesArrow->setPixmap(icon.pixmap(QSize(15, 15)));
984 mSubmodulesList->setVisible(!submodulesAreVisible);
985
986 GitQlientSettings settings(mGit->getGitDir());
987 settings.setLocalValue("SubmodulesHeader", !submodulesAreVisible);
988
989 emit panelsVisibilityChanged();
990}
991
992void BranchesWidget::onSubtreesHeaderClicked()
993{
994 const auto subtreesAreVisible = mSubtreeList->isVisible();
995 const auto icon = QIcon(subtreesAreVisible ? QString(":/icons/add") : QString(":/icons/remove"));
996 mSubtreeArrow->setPixmap(icon.pixmap(QSize(15, 15)));
997 mSubtreeList->setVisible(!subtreesAreVisible);
998
999 GitQlientSettings settings(mGit->getGitDir());
1000 settings.setLocalValue("SubtreeHeader", !subtreesAreVisible);
1001
1002 emit panelsVisibilityChanged();
1003}
1004
1005void BranchesWidget::onTagClicked(QTreeWidgetItem *item)
1006{
1007 if (item && item->data(0, IsLeaf).toBool())
1008 emit signalSelectCommit(item->data(0, ShaRole).toString());
1009}
1010
1011void BranchesWidget::onStashClicked(QListWidgetItem *item)
1012{
1013 onStashSelected(item->data(Qt::UserRole).toString());
1014}
1015
1016void BranchesWidget::onStashSelected(const QString &stashId)
1017{
1018 QScopedPointer<GitTags> git(new GitTags(mGit));
1019 const auto sha = git->getTagCommit(stashId).output;
1020
1021 emit signalSelectCommit(sha);
1022}
1023
1024void BranchesWidget::onSearchBranch()
1025{
1026 const auto lineEdit = qobject_cast<QLineEdit *>(sender());
1027
1028 const auto text = lineEdit->text();
1029
1030 if (mLastSearch != text)
1031 {
1032 mLastSearch = text;
1033 mLastIndex = mLocalBranchesTree->focusOnBranch(mLastSearch);
1034 mLastTreeSearched = mLocalBranchesTree;
1035
1036 if (mLastIndex == -1)
1037 {
1038 mLastIndex = mRemoteBranchesTree->focusOnBranch(mLastSearch);
1039 mLastTreeSearched = mRemoteBranchesTree;
1040
1041 if (mLastIndex == -1)
1042 {
1043 mLastIndex = mTagsTree->focusOnBranch(mLastSearch);
1044 mLastTreeSearched = mTagsTree;
1045
1046 if (mLastIndex == -1)
1047 mLastTreeSearched = mLocalBranchesTree;
1048 }
1049 }
1050 }
1051 else
1052 {
1053 if (mLastTreeSearched == mLocalBranchesTree)
1054 {
1055 if (mLastIndex != -1)
1056 {
1057 mLastIndex = mLocalBranchesTree->focusOnBranch(mLastSearch, mLastIndex);
1058 mLastTreeSearched = mLocalBranchesTree;
1059 }
1060
1061 if (mLastIndex == -1)
1062 {
1063 mLastIndex = mRemoteBranchesTree->focusOnBranch(mLastSearch);
1064 mLastTreeSearched = mRemoteBranchesTree;
1065 }
1066
1067 if (mLastIndex == -1)
1068 {
1069 mLastIndex = mTagsTree->focusOnBranch(mLastSearch);
1070 mLastTreeSearched = mTagsTree;
1071 }
1072 }
1073 else if (mLastTreeSearched == mRemoteBranchesTree)
1074 {
1075 if (mLastIndex == -1)
1076 {
1077 mLastIndex = mRemoteBranchesTree->focusOnBranch(mLastSearch);
1078 mLastTreeSearched = mRemoteBranchesTree;
1079 }
1080
1081 if (mLastIndex == -1)
1082 {
1083 mLastIndex = mTagsTree->focusOnBranch(mLastSearch);
1084 mLastTreeSearched = mTagsTree;
1085 }
1086 }
1087 else if (mLastIndex != -1)
1088 {
1089 mLastIndex = mTagsTree->focusOnBranch(mLastSearch, mLastIndex);
1090 mLastTreeSearched = mTagsTree;
1091
1092 if (mLastIndex == -1)
1093 mLastTreeSearched = mLocalBranchesTree;
1094 }
1095 }
1096}
1097
1098QPair<QString, QString> BranchesWidget::getSubtreeData(const QString &prefix)
1099{
1100 GitQlientSettings settings(mGit->getGitDir());
1101 bool end = false;
1102 QString url;
1103 QString ref;
1104
1105 for (auto i = 0; !end; ++i)
1106 {
1107 const auto repo = settings.localValue(QString("Subtrees/%1.prefix").arg(i), "");
1108
1109 if (repo.toString() == prefix)
1110 {
1111 auto tmpUrl = settings.localValue(QString("Subtrees/%1.url").arg(i)).toString();
1112 auto tmpRef = settings.localValue(QString("Subtrees/%1.ref").arg(i)).toString();
1113
1114 if (tmpUrl.isEmpty() || tmpRef.isEmpty())
1115 {
1116 const auto resp
1117 = QMessageBox::question(this, tr("Subtree configuration not found!"),
1118 tr("The subtree configuration was not found. It could be that it was created "
1119 "outside GitQlient.<br>To operate with this subtree, it needs to be "
1120 "configured.<br><br><b>Do you want to configure it now?<b>"));
1121
1122 if (resp == QMessageBox::Yes)
1123 {
1124 AddSubtreeDlg stDlg(prefix, mGit, this);
1125 const auto ret = stDlg.exec();
1126
1127 if (ret == QDialog::Accepted)
1128 {
1129 tmpUrl = settings.localValue(QString("Subtrees/%1.url").arg(i)).toString();
1130 tmpRef = settings.localValue(QString("Subtrees/%1.ref").arg(i)).toString();
1131
1132 if (tmpUrl.isEmpty() || tmpRef.isEmpty())
1133 QMessageBox::critical(this, tr("Unexpected error!"),
1134 tr("An unidentified error happened while using subtrees. Please contact the "
1135 "creator of GitQlient for support."));
1136 else
1137 {
1138 url = tmpUrl;
1139 ref = tmpRef;
1140 }
1141 }
1142 }
1143
1144 end = true;
1145 }
1146 else
1147 {
1148 url = tmpUrl;
1149 ref = tmpRef;
1150 end = true;
1151 }
1152 }
1153 else if (repo.toString().isEmpty())
1154 {
1155 const auto resp
1156 = QMessageBox::question(this, tr("Subtree configuration not found!"),
1157 tr("The subtree configuration was not found. It could be that it was created "
1158 "outside GitQlient.<br>To operate with this subtree, it needs to be "
1159 "configured.<br><br><b>Do you want to configure it now?<b>"));
1160
1161 if (resp == QMessageBox::Yes)
1162 {
1163 AddSubtreeDlg stDlg(prefix, mGit, this);
1164 const auto ret = stDlg.exec();
1165
1166 if (ret == QDialog::Accepted)
1167 {
1168 const auto tmpUrl = settings.localValue(QString("Subtrees/%1.url").arg(i)).toString();
1169 const auto tmpRef = settings.localValue(QString("Subtrees/%1.ref").arg(i)).toString();
1170
1171 if (tmpUrl.isEmpty() || tmpRef.isEmpty())
1172 QMessageBox::critical(this, tr("Unexpected error!"),
1173 tr("An unidentified error happened while using subtrees. Please contact the "
1174 "creator of GitQlient for support."));
1175 else
1176 {
1177 url = tmpUrl;
1178 ref = tmpRef;
1179 }
1180 }
1181 }
1182
1183 end = true;
1184 }
1185 }
1186
1187 return qMakePair(url, ref);
1188}
1189