forked from qt-creator/qt-creator
Use a singleton for the main settings, don't pass it around as shared pointer. Change-Id: I5c32679452ad631998a688afc9a6e2b154bf3a5d Reviewed-by: André Hartmann <aha_1980@gmx.de> Reviewed-by: Orgad Shaneh <orgads@gmail.com>
405 lines
14 KiB
C++
405 lines
14 KiB
C++
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#include "gerritplugin.h"
|
|
|
|
#include "gerritdialog.h"
|
|
#include "gerritmodel.h"
|
|
#include "gerritoptionspage.h"
|
|
#include "gerritparameters.h"
|
|
#include "gerritpushdialog.h"
|
|
|
|
#include "../gitclient.h"
|
|
#include "../gitplugin.h"
|
|
#include "../gittr.h"
|
|
|
|
#include <coreplugin/actionmanager/actioncontainer.h>
|
|
#include <coreplugin/actionmanager/actionmanager.h>
|
|
#include <coreplugin/documentmanager.h>
|
|
#include <coreplugin/icore.h>
|
|
#include <coreplugin/locator/commandlocator.h>
|
|
#include <coreplugin/progressmanager/processprogress.h>
|
|
#include <coreplugin/vcsmanager.h>
|
|
|
|
#include <utils/environment.h>
|
|
#include <utils/qtcprocess.h>
|
|
#include <utils/processinterface.h>
|
|
|
|
#include <vcsbase/vcsoutputwindow.h>
|
|
|
|
#include <QRegularExpression>
|
|
#include <QAction>
|
|
#include <QMessageBox>
|
|
#include <QMap>
|
|
|
|
using namespace Core;
|
|
using namespace Utils;
|
|
|
|
using namespace Git::Internal;
|
|
|
|
enum { debug = 0 };
|
|
|
|
namespace Gerrit {
|
|
namespace Constants {
|
|
const char GERRIT_OPEN_VIEW[] = "Gerrit.OpenView";
|
|
const char GERRIT_PUSH[] = "Gerrit.Push";
|
|
}
|
|
namespace Internal {
|
|
|
|
enum FetchMode
|
|
{
|
|
FetchDisplay,
|
|
FetchCherryPick,
|
|
FetchCheckout
|
|
};
|
|
|
|
/* FetchContext: Retrieves the patch and displays
|
|
* or applies it as desired. Does deleteLater() once it is done. */
|
|
|
|
class FetchContext : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
FetchContext(const std::shared_ptr<GerritChange> &change,
|
|
const FilePath &repository, const FilePath &git,
|
|
const GerritServer &server,
|
|
FetchMode fm, QObject *parent = nullptr);
|
|
void start();
|
|
|
|
private:
|
|
void processDone();
|
|
|
|
void show();
|
|
void cherryPick();
|
|
void checkout();
|
|
|
|
const std::shared_ptr<GerritChange> m_change;
|
|
const FilePath m_repository;
|
|
const FetchMode m_fetchMode;
|
|
const Utils::FilePath m_git;
|
|
const GerritServer m_server;
|
|
Process m_process;
|
|
};
|
|
|
|
FetchContext::FetchContext(const std::shared_ptr<GerritChange> &change,
|
|
const FilePath &repository, const FilePath &git,
|
|
const GerritServer &server,
|
|
FetchMode fm, QObject *parent)
|
|
: QObject(parent)
|
|
, m_change(change)
|
|
, m_repository(repository)
|
|
, m_fetchMode(fm)
|
|
, m_git(git)
|
|
, m_server(server)
|
|
{
|
|
m_process.setUseCtrlCStub(true);
|
|
connect(&m_process, &Process::done, this, &FetchContext::processDone);
|
|
connect(&m_process, &Process::readyReadStandardError, this, [this] {
|
|
VcsBase::VcsOutputWindow::append(QString::fromLocal8Bit(m_process.readAllRawStandardError()));
|
|
});
|
|
connect(&m_process, &Process::readyReadStandardOutput, this, [this] {
|
|
VcsBase::VcsOutputWindow::append(QString::fromLocal8Bit(m_process.readAllRawStandardOutput()));
|
|
});
|
|
m_process.setWorkingDirectory(repository);
|
|
m_process.setEnvironment(gitClient().processEnvironment(repository));
|
|
}
|
|
|
|
void FetchContext::start()
|
|
{
|
|
// Order: initialize future before starting the process in case error handling is invoked.
|
|
const CommandLine commandLine{m_git, m_change->gitFetchArguments(m_server)};
|
|
VcsBase::VcsOutputWindow::appendCommand(m_repository, commandLine);
|
|
m_process.setCommand(commandLine);
|
|
new ProcessProgress(&m_process);
|
|
m_process.start();
|
|
}
|
|
|
|
void FetchContext::processDone()
|
|
{
|
|
deleteLater();
|
|
|
|
if (m_process.result() != ProcessResult::FinishedWithSuccess) {
|
|
if (m_process.result() != ProcessResult::Canceled)
|
|
VcsBase::VcsOutputWindow::appendError(m_process.exitMessage());
|
|
return;
|
|
}
|
|
|
|
if (m_fetchMode == FetchDisplay)
|
|
show();
|
|
else if (m_fetchMode == FetchCherryPick)
|
|
cherryPick();
|
|
else if (m_fetchMode == FetchCheckout)
|
|
checkout();
|
|
}
|
|
|
|
void FetchContext::show()
|
|
{
|
|
const QString title = QString::number(m_change->number) + '/'
|
|
+ QString::number(m_change->currentPatchSet.patchSetNumber);
|
|
gitClient().show(m_repository, "FETCH_HEAD", title);
|
|
}
|
|
|
|
void FetchContext::cherryPick()
|
|
{
|
|
// Point user to errors.
|
|
VcsBase::VcsOutputWindow::instance()->popup(IOutputPane::ModeSwitch
|
|
| IOutputPane::WithFocus);
|
|
gitClient().synchronousCherryPick(m_repository, "FETCH_HEAD");
|
|
}
|
|
|
|
void FetchContext::checkout()
|
|
{
|
|
gitClient().checkout(m_repository, "FETCH_HEAD");
|
|
}
|
|
|
|
GerritPlugin::GerritPlugin()
|
|
: m_server(new GerritServer)
|
|
{
|
|
gerritSettings().fromSettings();
|
|
|
|
m_gerritOptionsPage = new GerritOptionsPage([this] {
|
|
if (m_dialog)
|
|
m_dialog->scheduleUpdateRemotes();
|
|
});
|
|
}
|
|
|
|
GerritPlugin::~GerritPlugin()
|
|
{
|
|
delete m_gerritOptionsPage;
|
|
}
|
|
|
|
void GerritPlugin::addToMenu(ActionContainer *ac)
|
|
{
|
|
|
|
QAction *openViewAction = new QAction(Git::Tr::tr("Gerrit..."), this);
|
|
|
|
m_gerritCommand =
|
|
ActionManager::registerAction(openViewAction, Constants::GERRIT_OPEN_VIEW);
|
|
connect(openViewAction, &QAction::triggered, this, &GerritPlugin::openView);
|
|
ac->addAction(m_gerritCommand);
|
|
|
|
QAction *pushAction = new QAction(Git::Tr::tr("Push to Gerrit..."), this);
|
|
|
|
m_pushToGerritCommand =
|
|
ActionManager::registerAction(pushAction, Constants::GERRIT_PUSH);
|
|
connect(pushAction, &QAction::triggered, this, [this] { push(); });
|
|
ac->addAction(m_pushToGerritCommand);
|
|
}
|
|
|
|
void GerritPlugin::updateActions(const VcsBase::VcsBasePluginState &state)
|
|
{
|
|
const bool hasTopLevel = state.hasTopLevel();
|
|
m_gerritCommand->action()->setEnabled(hasTopLevel);
|
|
m_pushToGerritCommand->action()->setEnabled(hasTopLevel);
|
|
if (m_dialog && m_dialog->isVisible())
|
|
m_dialog->setCurrentPath(state.topLevel());
|
|
}
|
|
|
|
void GerritPlugin::addToLocator(CommandLocator *locator)
|
|
{
|
|
locator->appendCommand(m_gerritCommand);
|
|
locator->appendCommand(m_pushToGerritCommand);
|
|
}
|
|
|
|
void GerritPlugin::push(const FilePath &topLevel)
|
|
{
|
|
// QScopedPointer is required to delete the dialog when leaving the function
|
|
GerritPushDialog dialog(topLevel, m_reviewers, ICore::dialogParent());
|
|
|
|
const QString initErrorMessage = dialog.initErrorMessage();
|
|
if (!initErrorMessage.isEmpty()) {
|
|
QMessageBox::warning(ICore::dialogParent(), Git::Tr::tr("Initialization Failed"), initErrorMessage);
|
|
return;
|
|
}
|
|
|
|
if (dialog.exec() == QDialog::Rejected)
|
|
return;
|
|
|
|
dialog.storeTopic();
|
|
m_reviewers = dialog.reviewers();
|
|
gitClient().push(topLevel, {dialog.selectedRemoteName(), dialog.pushTarget()});
|
|
}
|
|
|
|
static FilePath currentRepository()
|
|
{
|
|
return currentState().topLevel();
|
|
}
|
|
|
|
// Open or raise the Gerrit dialog window.
|
|
void GerritPlugin::openView()
|
|
{
|
|
if (m_dialog.isNull()) {
|
|
while (!gerritSettings().isValid()) {
|
|
QMessageBox::warning(Core::ICore::dialogParent(), Git::Tr::tr("Error"),
|
|
Git::Tr::tr("Invalid Gerrit configuration. Host, user and ssh binary are mandatory."));
|
|
if (!ICore::showOptionsDialog("Gerrit"))
|
|
return;
|
|
}
|
|
GerritDialog *gd = new GerritDialog(m_server, currentRepository(), ICore::dialogParent());
|
|
gd->setModal(false);
|
|
ICore::registerWindow(gd, Context("Git.Gerrit"));
|
|
connect(gd, &GerritDialog::fetchDisplay, this,
|
|
[this](const std::shared_ptr<GerritChange> &change) { fetch(change, FetchDisplay); });
|
|
connect(gd, &GerritDialog::fetchCherryPick, this,
|
|
[this](const std::shared_ptr<GerritChange> &change) { fetch(change, FetchCherryPick); });
|
|
connect(gd, &GerritDialog::fetchCheckout, this,
|
|
[this](const std::shared_ptr<GerritChange> &change) { fetch(change, FetchCheckout); });
|
|
connect(this, &GerritPlugin::fetchStarted, gd, &GerritDialog::fetchStarted);
|
|
connect(this, &GerritPlugin::fetchFinished, gd, &GerritDialog::fetchFinished);
|
|
m_dialog = gd;
|
|
} else {
|
|
m_dialog->setCurrentPath(currentRepository());
|
|
}
|
|
m_dialog->refresh();
|
|
const Qt::WindowStates state = m_dialog->windowState();
|
|
if (state & Qt::WindowMinimized)
|
|
m_dialog->setWindowState(state & ~Qt::WindowMinimized);
|
|
m_dialog->show();
|
|
m_dialog->raise();
|
|
}
|
|
|
|
void GerritPlugin::push()
|
|
{
|
|
push(currentRepository());
|
|
}
|
|
|
|
Utils::FilePath GerritPlugin::gitBinDirectory()
|
|
{
|
|
return gitClient().gitBinDirectory();
|
|
}
|
|
|
|
// Find the branch of a repository.
|
|
QString GerritPlugin::branch(const FilePath &repository)
|
|
{
|
|
return gitClient().synchronousCurrentLocalBranch(repository);
|
|
}
|
|
|
|
void GerritPlugin::fetch(const std::shared_ptr<GerritChange> &change, int mode)
|
|
{
|
|
// Locate git.
|
|
const Utils::FilePath git = gitClient().vcsBinary(m_dialog->repositoryPath());
|
|
if (git.isEmpty()) {
|
|
VcsBase::VcsOutputWindow::appendError(Git::Tr::tr("Git is not available."));
|
|
return;
|
|
}
|
|
|
|
FilePath repository;
|
|
bool verifiedRepository = false;
|
|
if (m_dialog && m_dialog->repositoryPath().exists())
|
|
repository = m_dialog->repositoryPath();
|
|
|
|
if (!repository.isEmpty()) {
|
|
// Check if remote from a working dir is the same as remote from patch
|
|
QMap<QString, QString> remotesList = gitClient().synchronousRemotesList(repository);
|
|
if (!remotesList.isEmpty()) {
|
|
const QStringList remotes = remotesList.values();
|
|
for (QString remote : remotes) {
|
|
if (remote.endsWith(".git"))
|
|
remote.chop(4);
|
|
if (remote.contains(m_server->host) && remote.endsWith(change->project)) {
|
|
verifiedRepository = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!verifiedRepository) {
|
|
const SubmoduleDataMap submodules = gitClient().submoduleList(repository);
|
|
for (const SubmoduleData &submoduleData : submodules) {
|
|
QString remote = submoduleData.url;
|
|
if (remote.endsWith(".git"))
|
|
remote.chop(4);
|
|
if (remote.contains(m_server->host) && remote.endsWith(change->project)
|
|
&& repository.pathAppended(submoduleData.dir).exists()) {
|
|
repository = repository.pathAppended(submoduleData.dir).cleanPath();
|
|
verifiedRepository = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!verifiedRepository) {
|
|
QMessageBox::StandardButton answer = QMessageBox::question(
|
|
ICore::dialogParent(), Git::Tr::tr("Remote Not Verified"),
|
|
Git::Tr::tr("Change host %1\nand project %2\n\nwere not verified among remotes"
|
|
" in %3. Select different folder?")
|
|
.arg(m_server->host,
|
|
change->project,
|
|
repository.toUserOutput()),
|
|
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
|
|
QMessageBox::Yes);
|
|
switch (answer) {
|
|
case QMessageBox::Cancel:
|
|
return;
|
|
case QMessageBox::No:
|
|
verifiedRepository = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!verifiedRepository) {
|
|
// Ask the user for a repository to retrieve the change.
|
|
const QString title =
|
|
Git::Tr::tr("Enter Local Repository for \"%1\" (%2)").arg(change->project, change->branch);
|
|
const FilePath suggestedRespository = findLocalRepository(change->project, change->branch);
|
|
repository = FileUtils::getExistingDirectory(m_dialog.data(), title, suggestedRespository);
|
|
}
|
|
|
|
if (repository.isEmpty())
|
|
return;
|
|
|
|
auto fc = new FetchContext(change, repository, git, *m_server, FetchMode(mode), this);
|
|
connect(fc, &QObject::destroyed, this, &GerritPlugin::fetchFinished);
|
|
emit fetchStarted(change);
|
|
fc->start();
|
|
}
|
|
|
|
// Try to find a matching repository for a project by asking the VcsManager.
|
|
FilePath GerritPlugin::findLocalRepository(const QString &project, const QString &branch) const
|
|
{
|
|
const FilePaths gitRepositories = VcsManager::repositories(versionControl());
|
|
// Determine key (file name) to look for (qt/qtbase->'qtbase').
|
|
const int slashPos = project.lastIndexOf('/');
|
|
const QString fixedProject = (slashPos < 0) ? project : project.mid(slashPos + 1);
|
|
// When looking at branch 1.7, try to check folders
|
|
// "qtbase_17", 'qtbase1.7' with a semi-smart regular expression.
|
|
QScopedPointer<QRegularExpression> branchRegexp;
|
|
if (!branch.isEmpty() && branch != "master") {
|
|
QString branchPattern = branch;
|
|
branchPattern.replace('.', "[\\.-_]?");
|
|
const QString pattern = '^' + fixedProject
|
|
+ "[-_]?"
|
|
+ branchPattern + '$';
|
|
branchRegexp.reset(new QRegularExpression(pattern));
|
|
if (!branchRegexp->isValid())
|
|
branchRegexp.reset(); // Oops.
|
|
}
|
|
for (const FilePath &repository : gitRepositories) {
|
|
const QString fileName = repository.fileName();
|
|
if ((!branchRegexp.isNull() && branchRegexp->match(fileName).hasMatch())
|
|
|| fileName == fixedProject) {
|
|
// Perform a check on the branch.
|
|
if (branch.isEmpty()) {
|
|
return repository;
|
|
} else {
|
|
const QString repositoryBranch = GerritPlugin::branch(repository);
|
|
if (repositoryBranch.isEmpty() || repositoryBranch == branch)
|
|
return repository;
|
|
} // !branch.isEmpty()
|
|
} // branchRegexp or file name match
|
|
} // for repositories
|
|
// No match, do we have a projects folder?
|
|
if (DocumentManager::useProjectsDirectory())
|
|
return DocumentManager::projectsDirectory();
|
|
|
|
return FilePath::currentWorkingPath();
|
|
}
|
|
|
|
} // namespace Internal
|
|
} // namespace Gerrit
|
|
|
|
#include "gerritplugin.moc"
|