1#include "BranchTreeWidget.h"
2
3#include <AddRemoteDlg.h>
4#include <BranchContextMenu.h>
5#include <GitBase.h>
6#include <GitBranches.h>
7#include <GitCache.h>
8#include <GitQlientBranchItemRole.h>
9#include <GitQlientStyles.h>
10#include <GitRemote.h>
11#include <PullDlg.h>
12
13#include <QApplication>
14#include <QMessageBox>
15
16using namespace GitQlient;
17
18RefTreeWidget::RefTreeWidget(QWidget *parent)
19 : QTreeWidget(parent)
20
21{
22 setContextMenuPolicy(Qt::CustomContextMenu);
23 setAttribute(Qt::WA_DeleteOnClose);
24}
25
26int RefTreeWidget::focusOnBranch(const QString &itemText, int startSearchPos)
27{
28 const auto items = findChildItem(itemText);
29
30 if (startSearchPos + 1 >= items.count())
31 return -1;
32
33 if (startSearchPos != -1)
34 {
35 auto itemToUnselect = items.at(startSearchPos);
36 itemToUnselect->setSelected(false);
37 }
38
39 ++startSearchPos;
40
41 auto itemToExpand = items.at(startSearchPos);
42 itemToExpand->setExpanded(true);
43 setCurrentItem(itemToExpand);
44 setCurrentIndex(indexFromItem(itemToExpand));
45
46 while (itemToExpand->parent())
47 {
48 itemToExpand->setExpanded(true);
49 itemToExpand = itemToExpand->parent();
50 }
51
52 itemToExpand->setExpanded(true);
53
54 return startSearchPos;
55}
56
57QVector<QTreeWidgetItem *> RefTreeWidget::findChildItem(const QString &text) const
58{
59 QModelIndexList indexes = model()->match(model()->index(0, 0, QModelIndex()), GitQlient::FullNameRole, text, -1,
60 Qt::MatchContains | Qt::MatchRecursive);
61 QVector<QTreeWidgetItem *> items;
62 const int indexesSize = indexes.size();
63 items.reserve(indexesSize);
64
65 for (int i = 0; i < indexesSize; ++i)
66 items.append(static_cast<QTreeWidgetItem *>(indexes.at(i).internalPointer()));
67
68 return items;
69}
70
71BranchTreeWidget::BranchTreeWidget(const QSharedPointer<GitCache> &cache, const QSharedPointer<GitBase> &git,
72 QWidget *parent)
73 : RefTreeWidget(parent)
74 , mCache(cache)
75 , mGit(git)
76{
77
78 connect(this, &BranchTreeWidget::customContextMenuRequested, this, &BranchTreeWidget::showBranchesContextMenu);
79 connect(this, &BranchTreeWidget::itemClicked, this, &BranchTreeWidget::selectCommit);
80 connect(this, &BranchTreeWidget::itemSelectionChanged, this, &BranchTreeWidget::onSelectionChanged);
81 connect(this, &BranchTreeWidget::itemDoubleClicked, this, &BranchTreeWidget::checkoutBranch);
82}
83
84void BranchTreeWidget::reloadCurrentBranchLink() const
85{
86 const auto items = findChildItem(mGit->getCurrentBranch());
87
88 if (!items.isEmpty())
89 {
90 items.at(0)->setData(0, GitQlient::ShaRole, mGit->getLastCommit().output.trimmed());
91 items.at(0)->setData(0, GitQlient::IsCurrentBranchRole, true);
92 }
93}
94
95void BranchTreeWidget::showBranchesContextMenu(const QPoint &pos)
96{
97 if (const auto item = itemAt(pos); item != nullptr)
98 {
99 auto selectedBranch = item->data(0, FullNameRole).toString();
100
101 if (!selectedBranch.isEmpty())
102 {
103 auto currentBranch = mGit->getCurrentBranch();
104
105 const auto menu = new BranchContextMenu({ currentBranch, selectedBranch, mLocal, mCache, mGit }, this);
106 connect(menu, &BranchContextMenu::signalRefreshPRsCache, this, &BranchTreeWidget::signalRefreshPRsCache);
107 connect(menu, &BranchContextMenu::signalFetchPerformed, this, &BranchTreeWidget::signalFetchPerformed);
108 connect(menu, &BranchContextMenu::logReload, this, &BranchTreeWidget::logReload);
109 connect(menu, &BranchContextMenu::fullReload, this, &BranchTreeWidget::fullReload);
110 connect(menu, &BranchContextMenu::signalCheckoutBranch, this, [this, item]() { checkoutBranch(item); });
111 connect(menu, &BranchContextMenu::signalMergeRequired, this, &BranchTreeWidget::signalMergeRequired);
112 connect(menu, &BranchContextMenu::mergeSqushRequested, this, &BranchTreeWidget::mergeSqushRequested);
113 connect(menu, &BranchContextMenu::signalPullConflict, this, &BranchTreeWidget::signalPullConflict);
114
115 menu->exec(viewport()->mapToGlobal(pos));
116 }
117 else if (item->data(0, IsRoot).toBool())
118 {
119 const auto menu = new QMenu(this);
120 const auto removeRemote = menu->addAction(tr("Remove remote"));
121 connect(removeRemote, &QAction::triggered, this, [this, item]() {
122 QScopedPointer<GitRemote> git(new GitRemote(mGit));
123 if (const auto ret = git->removeRemote(item->text(0)); ret.success)
124 {
125 mCache->deleteReference(item->data(0, ShaRole).toString(), References::Type::RemoteBranches,
126 item->text(0));
127 emit logReload();
128 }
129 });
130
131 menu->exec(viewport()->mapToGlobal(pos));
132 }
133 }
134 else if (!mLocal)
135 {
136 const auto menu = new QMenu(this);
137 const auto addRemote = menu->addAction(tr("Add remote"));
138 connect(addRemote, &QAction::triggered, this, [this]() {
139 const auto addRemote = new AddRemoteDlg(mGit);
140 const auto ret = addRemote->exec();
141
142 if (ret == QDialog::Accepted)
143 emit fullReload();
144 });
145
146 menu->exec(viewport()->mapToGlobal(pos));
147 }
148}
149
150void BranchTreeWidget::checkoutBranch(QTreeWidgetItem *item)
151{
152 if (item)
153 {
154 auto branchName = item->data(0, FullNameRole).toString();
155
156 if (!branchName.isEmpty())
157 {
158 const auto isLocal = item->data(0, LocalBranchRole).toBool();
159 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
160 QScopedPointer<GitBranches> git(new GitBranches(mGit));
161 const auto ret
162 = isLocal ? git->checkoutLocalBranch(branchName.remove("origin/")) : git->checkoutRemoteBranch(branchName);
163 QApplication::restoreOverrideCursor();
164
165 const auto output = ret.output;
166
167 if (ret.success)
168 {
169 QRegExp rx("by \\d+ commits");
170 rx.indexIn(output);
171 auto value = rx.capturedTexts().constFirst().split(" ");
172 auto uiUpdateRequested = false;
173
174 if (value.count() == 3 && output.contains("your branch is behind", Qt::CaseInsensitive))
175 {
176 PullDlg pull(mGit, output.split('\n').first());
177 connect(&pull, &PullDlg::signalRepositoryUpdated, this, &BranchTreeWidget::fullReload);
178 connect(&pull, &PullDlg::signalPullConflict, this, &BranchTreeWidget::signalPullConflict);
179
180 if (pull.exec() == QDialog::Accepted)
181 uiUpdateRequested = true;
182 }
183
184 if (!uiUpdateRequested)
185 {
186 if (auto oldItem = findChildItem(mGit->getCurrentBranch()); !oldItem.empty())
187 {
188 oldItem.at(0)->setData(0, GitQlient::IsCurrentBranchRole, false);
189 oldItem.clear();
190 oldItem.squeeze();
191 }
192 }
193
194 emit logReload();
195 }
196 else
197 {
198 QMessageBox msgBox(QMessageBox::Critical, tr("Error while checking out"),
199 tr("There were problems during the checkout operation. Please, see the detailed "
200 "description for more information."),
201 QMessageBox::Ok, this);
202 msgBox.setDetailedText(output);
203 msgBox.setStyleSheet(GitQlientStyles::getStyles());
204 msgBox.exec();
205 }
206 }
207 }
208}
209
210void BranchTreeWidget::selectCommit(QTreeWidgetItem *item)
211{
212 if (item && item->data(0, IsLeaf).toBool())
213 emit signalSelectCommit(item->data(0, ShaRole).toString());
214}
215
216void BranchTreeWidget::onSelectionChanged()
217{
218 const auto selection = selectedItems();
219
220 if (!selection.isEmpty())
221 selectCommit(selection.constFirst());
222}
223