1#include "GitQlientRepo.h"
2
3#include <BlameWidget.h>
4#include <BranchesWidget.h>
5#include <CommitHistoryColumns.h>
6#include <CommitInfo.h>
7#include <ConfigData.h>
8#include <ConfigWidget.h>
9#include <Controls.h>
10#include <DiffWidget.h>
11#include <GitBase.h>
12#include <GitCache.h>
13#include <GitConfig.h>
14#include <GitConfigDlg.h>
15#include <GitHistory.h>
16#include <GitHubRestApi.h>
17#include <GitLocal.h>
18#include <GitMerge.h>
19#include <GitQlientSettings.h>
20#include <GitRepoLoader.h>
21#include <GitServerCache.h>
22#include <GitServerWidget.h>
23#include <GitSubmodules.h>
24#include <GitTags.h>
25#include <GitWip.h>
26#include <HistoryWidget.h>
27#include <JenkinsWidget.h>
28#include <MergeWidget.h>
29#include <QLogger.h>
30#include <WaitingDlg.h>
31
32#include <QApplication>
33#include <QDirIterator>
34#include <QFileDialog>
35#include <QGridLayout>
36#include <QMessageBox>
37#include <QStackedLayout>
38#include <QStackedWidget>
39#include <QTimer>
40
41using namespace QLogger;
42using namespace GitServer;
43using namespace Jenkins;
44
45GitQlientRepo::GitQlientRepo(const QSharedPointer<GitBase> &git, const QSharedPointer<GitQlientSettings> &settings,
46 QWidget *parent)
47 : QFrame(parent)
48 , mGitQlientCache(new GitCache())
49 , mGitServerCache(new GitServerCache())
50 , mGitBase(git)
51 , mSettings(settings)
52 , mGitLoader(new GitRepoLoader(mGitBase, mGitQlientCache, mSettings))
53 , mHistoryWidget(new HistoryWidget(mGitQlientCache, mGitBase, mGitServerCache, mSettings))
54 , mStackedLayout(new QStackedLayout())
55 , mControls(new Controls(mGitQlientCache, mGitBase))
56 , mDiffWidget(new DiffWidget(mGitBase, mGitQlientCache))
57 , mBlameWidget(new BlameWidget(mGitQlientCache, mGitBase, mSettings))
58 , mMergeWidget(new MergeWidget(mGitQlientCache, mGitBase))
59 , mGitServerWidget(new GitServerWidget(mGitQlientCache, mGitBase, mGitServerCache))
60 , mJenkins(new JenkinsWidget(mGitBase))
61 , mConfigWidget(new ConfigWidget(mGitBase))
62 , mAutoFetch(new QTimer())
63 , mAutoFilesUpdate(new QTimer())
64 , mGitTags(new GitTags(mGitBase, mGitQlientCache))
65{
66 setAttribute(Qt::WA_DeleteOnClose);
67
68 QLog_Info("UI", QString("Initializing GitQlient"));
69
70 setObjectName("mainWindow");
71 setWindowTitle("GitQlient");
72 setAttribute(Qt::WA_DeleteOnClose);
73
74 QScopedPointer<GitConfig> gitConfig(new GitConfig(mGitBase));
75 const auto serverUrl = gitConfig->getServerHost();
76 const auto repoInfo = gitConfig->getCurrentRepoAndOwner();
77
78 mGitServerCache->init(serverUrl, repoInfo);
79
80 mHistoryWidget->setContentsMargins(QMargins(5, 5, 5, 5));
81 mDiffWidget->setContentsMargins(QMargins(5, 5, 5, 5));
82 mBlameWidget->setContentsMargins(QMargins(5, 5, 5, 5));
83 mMergeWidget->setContentsMargins(QMargins(5, 5, 5, 5));
84 mGitServerWidget->setContentsMargins(QMargins(5, 5, 5, 5));
85 mJenkins->setContentsMargins(QMargins(5, 5, 5, 5));
86 mConfigWidget->setContentsMargins(QMargins(5, 5, 5, 5));
87
88 mStackedLayout->addWidget(mHistoryWidget);
89 mStackedLayout->addWidget(mDiffWidget);
90 mStackedLayout->addWidget(mBlameWidget);
91 mStackedLayout->addWidget(mMergeWidget);
92 mStackedLayout->addWidget(mGitServerWidget);
93 mStackedLayout->addWidget(mJenkins);
94 mStackedLayout->addWidget(mConfigWidget);
95
96 const auto mainLayout = new QVBoxLayout();
97 mainLayout->setSpacing(0);
98 mainLayout->setContentsMargins(QMargins());
99 mainLayout->addWidget(mControls);
100 mainLayout->addLayout(mStackedLayout);
101
102 setLayout(mainLayout);
103
104 showHistoryView();
105
106 const auto fetchInterval = mSettings->localValue("AutoFetch", 5).toInt();
107
108 mAutoFetch->setInterval(fetchInterval * 60 * 1000);
109 mAutoFilesUpdate->setInterval(15000);
110
111 connect(mAutoFetch, &QTimer::timeout, mControls, &Controls::fetchAll);
112 connect(mAutoFilesUpdate, &QTimer::timeout, this, &GitQlientRepo::updateUiFromWatcher);
113
114 connect(mControls, &Controls::requestFullReload, this, &GitQlientRepo::fullReload);
115 connect(mControls, &Controls::requestReferencesReload, this, &GitQlientRepo::referencesReload);
116
117 connect(mControls, &Controls::signalGoRepo, this, &GitQlientRepo::showHistoryView);
118 connect(mControls, &Controls::signalGoBlame, this, &GitQlientRepo::showBlameView);
119 connect(mControls, &Controls::signalGoDiff, this, &GitQlientRepo::showDiffView);
120 connect(mControls, &Controls::signalGoMerge, this, &GitQlientRepo::showMergeView);
121 connect(mControls, &Controls::signalGoServer, this, &GitQlientRepo::showGitServerView);
122 connect(mControls, &Controls::signalGoBuildSystem, this, &GitQlientRepo::showBuildSystemView);
123 connect(mControls, &Controls::goConfig, this, &GitQlientRepo::showConfig);
124 connect(mControls, &Controls::signalPullConflict, mControls, &Controls::activateMergeWarning);
125 connect(mControls, &Controls::signalPullConflict, this, &GitQlientRepo::showWarningMerge);
126
127 connect(mHistoryWidget, &HistoryWidget::signalAllBranchesActive, mGitLoader.data(), &GitRepoLoader::setShowAll);
128 connect(mHistoryWidget, &HistoryWidget::fullReload, this, &GitQlientRepo::fullReload);
129 connect(mHistoryWidget, &HistoryWidget::referencesReload, this, &GitQlientRepo::referencesReload);
130 connect(mHistoryWidget, &HistoryWidget::logReload, this, &GitQlientRepo::logReload);
131
132 connect(mHistoryWidget, &HistoryWidget::panelsVisibilityChanged, mConfigWidget,
133 &ConfigWidget::onPanelsVisibilityChanged);
134 connect(mHistoryWidget, &HistoryWidget::signalOpenSubmodule, this, &GitQlientRepo::signalOpenSubmodule);
135 connect(mHistoryWidget, &HistoryWidget::signalOpenDiff, this, &GitQlientRepo::openCommitDiff);
136 connect(mHistoryWidget, &HistoryWidget::signalOpenCompareDiff, this, &GitQlientRepo::openCommitCompareDiff);
137 connect(mHistoryWidget, &HistoryWidget::signalShowDiff, this, &GitQlientRepo::loadFileDiff);
138 connect(mHistoryWidget, &HistoryWidget::changesCommitted, this, &GitQlientRepo::onChangesCommitted);
139 connect(mHistoryWidget, &HistoryWidget::signalShowFileHistory, this, &GitQlientRepo::showFileHistory);
140 connect(mHistoryWidget, &HistoryWidget::signalMergeConflicts, mControls, &Controls::activateMergeWarning);
141 connect(mHistoryWidget, &HistoryWidget::signalMergeConflicts, this, &GitQlientRepo::showWarningMerge);
142 connect(mHistoryWidget, &HistoryWidget::signalCherryPickConflict, mControls, &Controls::activateMergeWarning);
143 connect(mHistoryWidget, &HistoryWidget::signalCherryPickConflict, this, &GitQlientRepo::showCherryPickConflict);
144 connect(mHistoryWidget, &HistoryWidget::signalPullConflict, mControls, &Controls::activateMergeWarning);
145 connect(mHistoryWidget, &HistoryWidget::signalPullConflict, this, &GitQlientRepo::showWarningMerge);
146 connect(mHistoryWidget, &HistoryWidget::signalUpdateWip, this, &GitQlientRepo::updateWip);
147 connect(mHistoryWidget, &HistoryWidget::showPrDetailedView, this, &GitQlientRepo::showGitServerPrView);
148
149 connect(mDiffWidget, &DiffWidget::signalShowFileHistory, this, &GitQlientRepo::showFileHistory);
150 connect(mDiffWidget, &DiffWidget::signalDiffEmpty, mControls, &Controls::disableDiff);
151 connect(mDiffWidget, &DiffWidget::signalDiffEmpty, this, &GitQlientRepo::showPreviousView);
152
153 connect(mBlameWidget, &BlameWidget::showFileDiff, this, &GitQlientRepo::loadFileDiff);
154 connect(mBlameWidget, &BlameWidget::signalOpenDiff, this, &GitQlientRepo::openCommitCompareDiff);
155
156 connect(mMergeWidget, &MergeWidget::signalMergeFinished, this, &GitQlientRepo::showHistoryView);
157 connect(mMergeWidget, &MergeWidget::signalMergeFinished, mGitLoader.data(), &GitRepoLoader::loadAll);
158 connect(mMergeWidget, &MergeWidget::signalMergeFinished, mControls, &Controls::disableMergeWarning);
159
160 connect(mConfigWidget, &ConfigWidget::commitTitleMaxLenghtChanged, mHistoryWidget,
161 &HistoryWidget::onCommitTitleMaxLenghtChanged);
162 connect(mConfigWidget, &ConfigWidget::panelsVisibilityChaned, mHistoryWidget,
163 &HistoryWidget::onPanelsVisibilityChanged);
164
165 connect(mGitServerWidget, &GitServerWidget::openDiff, this, &GitQlientRepo::openCommitDiff);
166
167 connect(mJenkins, &JenkinsWidget::gotoBranch, this, &GitQlientRepo::focusHistoryOnBranch);
168 connect(mJenkins, &JenkinsWidget::gotoPullRequest, this, &GitQlientRepo::focusHistoryOnPr);
169
170 connect(mGitLoader.data(), &GitRepoLoader::signalLoadingStarted, this, &GitQlientRepo::createProgressDialog);
171 connect(mGitLoader.data(), &GitRepoLoader::signalLoadingFinished, this, &GitQlientRepo::onRepoLoadFinished);
172
173 m_loaderThread = new QThread();
174 mGitLoader->moveToThread(m_loaderThread);
175 mGitQlientCache->moveToThread(m_loaderThread);
176 connect(this, &GitQlientRepo::fullReload, mGitLoader.data(), &GitRepoLoader::loadAll);
177 connect(this, &GitQlientRepo::referencesReload, mGitLoader.data(), &GitRepoLoader::loadReferences);
178 connect(this, &GitQlientRepo::logReload, mGitLoader.data(), &GitRepoLoader::loadLogHistory);
179 m_loaderThread->start();
180
181 mGitLoader->setShowAll(mSettings->localValue("ShowAllBranches", true).toBool());
182}
183
184GitQlientRepo::~GitQlientRepo()
185{
186 delete mAutoFetch;
187 delete mAutoFilesUpdate;
188
189 m_loaderThread->exit();
190 m_loaderThread->wait();
191 delete m_loaderThread;
192}
193
194QString GitQlientRepo::currentBranch() const
195{
196 return mGitBase->getCurrentBranch();
197}
198
199void GitQlientRepo::updateUiFromWatcher()
200{
201 QLog_Info("UI", QString("Updating the GitQlient UI from watcher"));
202
203 QScopedPointer<GitWip> git(new GitWip(mGitBase, mGitQlientCache));
204 git->updateWip();
205
206 mHistoryWidget->updateUiFromWatcher();
207
208 mDiffWidget->reload();
209}
210
211void GitQlientRepo::setRepository(const QString &newDir)
212{
213 if (!newDir.isEmpty())
214 {
215 QLog_Info("UI", QString("Loading repository at {%1}...").arg(newDir));
216
217 mGitLoader->cancelAll();
218
219 emit fullReload();
220
221 mCurrentDir = newDir;
222 clearWindow();
223 setWidgetsEnabled(false);
224 }
225 else
226 {
227 QLog_Info("UI", QString("Repository is empty. Cleaning GitQlient"));
228
229 mCurrentDir = "";
230 clearWindow();
231 setWidgetsEnabled(false);
232 }
233}
234
235void GitQlientRepo::clearWindow()
236{
237 blockSignals(true);
238
239 mHistoryWidget->clear();
240 mDiffWidget->clear();
241
242 blockSignals(false);
243}
244
245void GitQlientRepo::setWidgetsEnabled(bool enabled)
246{
247 mControls->enableButtons(enabled);
248 mHistoryWidget->setEnabled(enabled);
249 mDiffWidget->setEnabled(enabled);
250}
251
252void GitQlientRepo::showFileHistory(const QString &fileName)
253{
254 mBlameWidget->showFileHistory(fileName);
255
256 showBlameView();
257}
258
259void GitQlientRepo::createProgressDialog()
260{
261 if (!mWaitDlg)
262 {
263 mWaitDlg = new WaitingDlg(tr("Loading repository..."));
264 mWaitDlg->setWindowFlag(Qt::Tool);
265 mWaitDlg->open();
266
267 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
268 }
269}
270
271void GitQlientRepo::onRepoLoadFinished(bool fullReload)
272{
273 mGitTags->getRemoteTags();
274
275 if (!mIsInit)
276 {
277 mIsInit = true;
278
279 mCurrentDir = mGitBase->getWorkingDir();
280
281 emit repoOpened(mCurrentDir);
282
283 setWidgetsEnabled(true);
284
285 mBlameWidget->init(mCurrentDir);
286
287 mControls->enableButtons(true);
288
289 mAutoFilesUpdate->start();
290
291 QScopedPointer<GitConfig> git(new GitConfig(mGitBase));
292
293 if (!git->getGlobalUserInfo().isValid() && !git->getLocalUserInfo().isValid())
294 {
295 QLog_Info("UI", QString("Configuring Git..."));
296
297 GitConfigDlg configDlg(mGitBase);
298
299 configDlg.exec();
300
301 QLog_Info("UI", QString("... Git configured!"));
302 }
303
304 QLog_Info("UI", "... repository loaded successfully");
305 }
306
307 const auto totalCommits = mGitQlientCache->commitCount();
308
309 mHistoryWidget->loadBranches(fullReload);
310 mHistoryWidget->updateGraphView(totalCommits);
311
312 mBlameWidget->onNewRevisions(totalCommits);
313
314 mDiffWidget->reload();
315
316 if (mWaitDlg)
317 mWaitDlg->close();
318
319 if (QScopedPointer<GitMerge> gitMerge(new GitMerge(mGitBase, mGitQlientCache)); gitMerge->isInMerge())
320 {
321 mControls->activateMergeWarning();
322 showWarningMerge();
323
324 QMessageBox::warning(this, tr("Merge in progress"),
325 tr("There is a merge conflict in progress. Solve the merge before moving on."));
326 }
327 else if (QScopedPointer<GitLocal> gitMerge(new GitLocal(mGitBase)); gitMerge->isInCherryPickMerge())
328 {
329 mControls->activateMergeWarning();
330 showCherryPickConflict();
331
332 QMessageBox::warning(
333 this, tr("Cherry-pick in progress"),
334 tr("There is a cherry-pick in progress that contains with conflicts. Solve them before moving on."));
335 }
336
337 emit currentBranchChanged();
338}
339
340void GitQlientRepo::loadFileDiff(const QString &currentSha, const QString &previousSha, const QString &file,
341 bool isCached)
342{
343 const auto loaded = mDiffWidget->loadFileDiff(currentSha, previousSha, file, isCached);
344
345 if (loaded)
346 {
347 mControls->enableDiff();
348 showDiffView();
349 }
350}
351
352void GitQlientRepo::showHistoryView()
353{
354 mPreviousView = qMakePair(mControls->getCurrentSelectedButton(), mStackedLayout->currentWidget());
355
356 mStackedLayout->setCurrentWidget(mHistoryWidget);
357 mControls->toggleButton(ControlsMainViews::History);
358}
359
360void GitQlientRepo::showBlameView()
361{
362 mPreviousView = qMakePair(mControls->getCurrentSelectedButton(), mStackedLayout->currentWidget());
363
364 mStackedLayout->setCurrentWidget(mBlameWidget);
365 mControls->toggleButton(ControlsMainViews::Blame);
366}
367
368void GitQlientRepo::showDiffView()
369{
370 mPreviousView = qMakePair(mControls->getCurrentSelectedButton(), mStackedLayout->currentWidget());
371
372 mStackedLayout->setCurrentWidget(mDiffWidget);
373 mControls->toggleButton(ControlsMainViews::Diff);
374}
375
376// TODO: Optimize
377void GitQlientRepo::showWarningMerge()
378{
379 showMergeView();
380
381 const auto wipCommit = mGitQlientCache->commitInfo(CommitInfo::ZERO_SHA);
382
383 QScopedPointer<GitWip> git(new GitWip(mGitBase, mGitQlientCache));
384 git->updateWip();
385
386 const auto file = mGitQlientCache->revisionFile(CommitInfo::ZERO_SHA, wipCommit.firstParent());
387
388 if (file)
389 mMergeWidget->configure(file.value(), MergeWidget::ConflictReason::Merge);
390}
391
392// TODO: Optimize
393void GitQlientRepo::showCherryPickConflict(const QStringList &shas)
394{
395 showMergeView();
396
397 const auto wipCommit = mGitQlientCache->commitInfo(CommitInfo::ZERO_SHA);
398
399 QScopedPointer<GitWip> git(new GitWip(mGitBase, mGitQlientCache));
400 git->updateWip();
401
402 const auto files = mGitQlientCache->revisionFile(CommitInfo::ZERO_SHA, wipCommit.firstParent());
403
404 if (files)
405 mMergeWidget->configureForCherryPick(files.value(), shas);
406}
407
408// TODO: Optimize
409void GitQlientRepo::showPullConflict()
410{
411 showMergeView();
412
413 const auto wipCommit = mGitQlientCache->commitInfo(CommitInfo::ZERO_SHA);
414
415 QScopedPointer<GitWip> git(new GitWip(mGitBase, mGitQlientCache));
416 git->updateWip();
417
418 const auto files = mGitQlientCache->revisionFile(CommitInfo::ZERO_SHA, wipCommit.firstParent());
419
420 if (files)
421 mMergeWidget->configure(files.value(), MergeWidget::ConflictReason::Pull);
422}
423
424void GitQlientRepo::showMergeView()
425{
426 mStackedLayout->setCurrentWidget(mMergeWidget);
427 mControls->toggleButton(ControlsMainViews::Merge);
428}
429
430bool GitQlientRepo::configureGitServer() const
431{
432 bool isConfigured = false;
433
434 if (!mGitServerWidget->isConfigured())
435 {
436 QScopedPointer<GitConfig> gitConfig(new GitConfig(mGitBase));
437 const auto serverUrl = gitConfig->getServerHost();
438 const auto repoInfo = gitConfig->getCurrentRepoAndOwner();
439
440 GitQlientSettings settings("");
441 const auto user = settings.globalValue(QString("%1/user").arg(serverUrl)).toString();
442 const auto token = settings.globalValue(QString("%1/token").arg(serverUrl)).toString();
443
444 GitServer::ConfigData data;
445 data.user = user;
446 data.token = token;
447 data.serverUrl = serverUrl;
448 data.repoInfo = repoInfo;
449
450 isConfigured = mGitServerWidget->configure(data);
451 }
452 else
453 isConfigured = true;
454
455 return isConfigured;
456}
457
458void GitQlientRepo::showGitServerView()
459{
460 if (configureGitServer())
461 {
462 mStackedLayout->setCurrentWidget(mGitServerWidget);
463 mControls->toggleButton(ControlsMainViews::GitServer);
464 }
465 else
466 showPreviousView();
467}
468
469void GitQlientRepo::showGitServerPrView(int prNumber)
470{
471 if (configureGitServer())
472 {
473 showGitServerView();
474 mGitServerWidget->openPullRequest(prNumber);
475 }
476}
477
478void GitQlientRepo::showBuildSystemView()
479{
480 mJenkins->reload();
481 mStackedLayout->setCurrentWidget(mJenkins);
482 mControls->toggleButton(ControlsMainViews::BuildSystem);
483}
484
485void GitQlientRepo::showConfig()
486{
487 mStackedLayout->setCurrentWidget(mConfigWidget);
488 mControls->toggleButton(ControlsMainViews::Config);
489}
490
491void GitQlientRepo::showPreviousView()
492{
493 mStackedLayout->setCurrentWidget(mPreviousView.second);
494 mControls->toggleButton(mPreviousView.first);
495}
496
497void GitQlientRepo::updateWip()
498{
499 mHistoryWidget->resetWip();
500
501 QScopedPointer<GitWip> git(new GitWip(mGitBase, mGitQlientCache));
502 git->updateWip();
503
504 mHistoryWidget->updateUiFromWatcher();
505}
506
507void GitQlientRepo::focusHistoryOnBranch(const QString &branch)
508{
509 auto found = false;
510 const auto fullBranch = QString("origin/%1").arg(branch);
511 auto remoteBranches = mGitQlientCache->getBranches(References::Type::RemoteBranches);
512
513 for (const auto &remote : remoteBranches)
514 {
515 if (remote.second.contains(fullBranch))
516 {
517 found = true;
518 mHistoryWidget->focusOnCommit(remote.first);
519 showHistoryView();
520 }
521 }
522
523 remoteBranches.clear();
524 remoteBranches.squeeze();
525
526 if (!found)
527 QMessageBox::information(
528 this, tr("Branch not found"),
529 tr("The branch couldn't be found. Please, make sure you fetched and have the latest changes."));
530}
531
532void GitQlientRepo::focusHistoryOnPr(int prNumber)
533{
534 const auto pr = mGitServerCache->getPullRequest(prNumber);
535
536 mHistoryWidget->focusOnCommit(pr.state.sha);
537 showHistoryView();
538}
539
540void GitQlientRepo::openCommitDiff(const QString currentSha)
541{
542 const auto rev = mGitQlientCache->commitInfo(currentSha);
543 const auto loaded = mDiffWidget->loadCommitDiff(currentSha, rev.firstParent());
544
545 if (loaded)
546 {
547 mControls->enableDiff();
548
549 showDiffView();
550 }
551}
552
553void GitQlientRepo::openCommitCompareDiff(const QStringList &shas)
554{
555 const auto loaded = mDiffWidget->loadCommitDiff(shas.last(), shas.first());
556
557 if (loaded)
558 {
559 mControls->enableDiff();
560
561 showDiffView();
562 }
563}
564
565void GitQlientRepo::onChangesCommitted()
566{
567 mHistoryWidget->selectCommit(CommitInfo::ZERO_SHA);
568 mHistoryWidget->loadBranches(false);
569 showHistoryView();
570}
571
572void GitQlientRepo::closeEvent(QCloseEvent *ce)
573{
574 QLog_Info("UI", QString("Closing GitQlient for repository {%1}").arg(mCurrentDir));
575
576 mGitLoader->cancelAll();
577
578 QWidget::closeEvent(ce);
579}
580