diff options
author | Friedemann Kleint <Friedemann.Kleint@nokia.com> | 2012-04-20 17:47:37 +0200 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@nokia.com> | 2012-04-26 14:34:41 +0200 |
commit | 2193915cca82693abd0cbe69670dfee14212b6bf (patch) | |
tree | 29e9c19219898e429ec281676ce8dfd049e0d5a3 /src/plugins/git/gerrit/gerritplugin.cpp | |
parent | 2b88fedc472543700e76f965a93d6110094ff59b (diff) | |
download | qt-creator-2193915cca82693abd0cbe69670dfee14212b6bf.tar.gz |
Start on a gerrit plugin as a sub-plugin of git.
- Add a gerrit window that shows a list of changes
and buttons to display, apply or check out
a change.
- Uses the new Qt 5 Json API or the utils/json
classes for Qt 4.
Tested-by: Tobias Hunger <tobias.hunger@nokia.com>
Tested-by: Orgad Shaneh <orgads@gmail.com>
Change-Id: I14c6c2c2de8f95fb785752c7319be8638b386a1e
Reviewed-by: Eike Ziller <eike.ziller@nokia.com>
Reviewed-by: Tobias Hunger <tobias.hunger@nokia.com>
Diffstat (limited to 'src/plugins/git/gerrit/gerritplugin.cpp')
-rw-r--r-- | src/plugins/git/gerrit/gerritplugin.cpp | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/src/plugins/git/gerrit/gerritplugin.cpp b/src/plugins/git/gerrit/gerritplugin.cpp new file mode 100644 index 0000000000..79362839a5 --- /dev/null +++ b/src/plugins/git/gerrit/gerritplugin.cpp @@ -0,0 +1,488 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "gerritplugin.h" +#include "gerritparameters.h" +#include "gerritdialog.h" +#include "gerritmodel.h" +#include "gerritoptionspage.h" + +#include <gitplugin.h> +#include <gitclient.h> +#include <gitversioncontrol.h> +#include <vcsbase/vcsbaseconstants.h> + +#include <coreplugin/icore.h> +#include <coreplugin/coreconstants.h> +#include <coreplugin/vcsmanager.h> +#include <coreplugin/progressmanager/progressmanager.h> +#include <coreplugin/progressmanager/futureprogress.h> +#include <coreplugin/documentmanager.h> +#include <coreplugin/actionmanager/actionmanager.h> +#include <coreplugin/actionmanager/actioncontainer.h> +#include <coreplugin/actionmanager/command.h> +#include <coreplugin/editormanager/editormanager.h> + +#include <vcsbase/vcsbaseoutputwindow.h> + +#include <utils/synchronousprocess.h> + +#include <QDebug> +#include <QProcess> +#include <QRegExp> +#include <QMainWindow> +#include <QAction> +#include <QFileDialog> +#include <QTemporaryFile> +#include <QDir> + +enum { debug = 0 }; + +namespace Gerrit { +namespace Constants { +const char GERRIT_OPEN_VIEW[] = "Gerrit.OpenView"; +} +namespace Internal { + +enum FetchMode +{ + FetchDisplay, + FetchApply, + 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 QSharedPointer<GerritChange> &change, + const QString &repository, const QString &git, + const QSharedPointer<GerritParameters> &p, + FetchMode fm, QObject *parent = 0); + ~FetchContext(); + +public slots: + void start(); + +private slots: + void processError(QProcess::ProcessError); + void processFinished(int exitCode, QProcess::ExitStatus); + void processReadyReadStandardError(); + void processReadyReadStandardOutput(); + +private: + // State enumeration. It starts in 'FetchState' and then + // branches to 'WritePatchFileState', 'CherryPickState' + // or 'CheckoutState' depending on FetchMode. + enum State + { + FetchState, // Fetch patch + WritePatchFileState, // Write patch to a file + CherryPickState, // Cherry-pick (apply) fetch-head + CheckoutState, // Check out fetch-head + DoneState, + ErrorState + }; + + void handleError(const QString &message); + void startWritePatchFile(); + void startCherryPick(); + void startCheckout(); + + const QSharedPointer<GerritChange> m_change; + const QString m_repository; + const FetchMode m_fetchMode; + const QString m_git; + const QSharedPointer<GerritParameters> m_parameters; + QScopedPointer<QTemporaryFile> m_patchFile; + QString m_patchFileName; + State m_state; + QProcess m_process; + QFutureInterface<void> m_progress; +}; + +FetchContext::FetchContext(const QSharedPointer<GerritChange> &change, + const QString &repository, const QString &git, + const QSharedPointer<GerritParameters> &p, + FetchMode fm, QObject *parent) + : QObject(parent) + , m_change(change) + , m_repository(repository) + , m_fetchMode(fm) + , m_git(git) + , m_parameters(p) + , m_state(FetchState) +{ + connect(&m_process, SIGNAL(error(QProcess::ProcessError)), + this, SLOT(processError(QProcess::ProcessError))); + connect(&m_process, SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(processFinished(int,QProcess::ExitStatus))); + connect(&m_process, SIGNAL(readyReadStandardError()), + this, SLOT(processReadyReadStandardError())); + connect(&m_process, SIGNAL(readyReadStandardOutput()), + this, SLOT(processReadyReadStandardOutput())); + m_process.setWorkingDirectory(repository); +} + +FetchContext::~FetchContext() +{ + if (m_progress.isRunning()) + m_progress.reportFinished(); + m_process.disconnect(this); + Utils::SynchronousProcess::stopProcess(m_process); +} + +void FetchContext::start() +{ + m_progress.setProgressRange(0, 2); + Core::ProgressManager *pm = Core::ICore::instance()->progressManager(); + Core::FutureProgress *fp = pm->addTask(m_progress.future(), tr("Gerrit Fetch"), + QLatin1String("gerrit-fetch")); + fp->setKeepOnFinish(Core::FutureProgress::HideOnFinish); + m_progress.reportStarted(); + // Order: initialize future before starting the process in case error handling is invoked. + const QStringList args = m_change->gitFetchArguments(m_parameters); + VcsBase::VcsBaseOutputWindow::instance()->appendCommand(m_repository, m_git, args); + m_process.start(m_git, args); + m_process.closeWriteChannel(); +} + +void FetchContext::processFinished(int exitCode, QProcess::ExitStatus es) +{ + if (es != QProcess::NormalExit) { + handleError(tr("%1 crashed.").arg(m_git)); + return; + } + if (exitCode) { + handleError(tr("%1 returned %2.").arg(m_git).arg(exitCode)); + return; + } + switch (m_state) { + case DoneState: + case ErrorState: + break; + case FetchState: + m_progress.setProgressValue(m_progress.progressValue() + 1); + switch (m_fetchMode) { + case FetchDisplay: + m_state = WritePatchFileState; + startWritePatchFile(); + break; + case FetchApply: + m_state = CherryPickState; + startCherryPick(); + break; + case FetchCheckout: + m_state = CheckoutState; + startCheckout(); + break; + } // switch (m_fetchMode) + break; + case WritePatchFileState: + switch (m_fetchMode) { + case FetchDisplay: + m_patchFileName = m_patchFile->fileName(); + m_patchFile->close(); + m_patchFile.reset(); + m_state = DoneState; + m_progress.reportFinished(); + Core::EditorManager::instance()->openEditor(m_patchFileName); + deleteLater(); + break; + default: + break; + } + break; + case CherryPickState: + case CheckoutState: + m_progress.reportFinished(); + m_state = DoneState; + deleteLater(); + } +} + +void FetchContext::processReadyReadStandardError() +{ + // Note: fetch displays progress on stderr. + const QString errorOutput = QString::fromLocal8Bit(m_process.readAllStandardError()); + if (m_state == FetchState || m_state == CheckoutState) { + VcsBase::VcsBaseOutputWindow::instance()->append(errorOutput); + } else { + VcsBase::VcsBaseOutputWindow::instance()->appendError(errorOutput); + } +} + +void FetchContext::processReadyReadStandardOutput() +{ + const QByteArray output = m_process.readAllStandardOutput(); + if (m_state == WritePatchFileState) { + m_patchFile->write(output); + } else { + VcsBase::VcsBaseOutputWindow::instance()->append(QString::fromLocal8Bit(output)); + } +} + +void FetchContext::handleError(const QString &e) +{ + m_state = ErrorState; + VcsBase::VcsBaseOutputWindow::instance()->appendError(e); + m_progress.reportCanceled(); + m_progress.reportFinished(); + deleteLater(); +} + +void FetchContext::processError(QProcess::ProcessError e) +{ + const QString msg = tr("Error running %1: %2").arg(m_git, m_process.errorString()); + if (e == QProcess::FailedToStart) { + handleError(msg); + } else { + VcsBase::VcsBaseOutputWindow::instance()->appendError(msg); + } +} + +void FetchContext::startWritePatchFile() +{ + // Fetch to file in temporary folder. + QString tempPattern = QDir::tempPath(); + if (!tempPattern.endsWith(QLatin1Char('/'))) + tempPattern += QLatin1Char('/'); + tempPattern += QLatin1String("gerrit_") + QString::number(m_change->number) + + QLatin1Char('_') + + QString::number(m_change->currentPatchSet.patchSetNumber) + + QLatin1String("XXXXXX.patch"); + m_patchFile.reset(new QTemporaryFile(tempPattern)); + m_patchFile->setAutoRemove(false); + if (!m_patchFile->open()) { + handleError(tr("Error writing to temporary file.")); + return; + } + VcsBase::VcsBaseOutputWindow::instance()->append(tr("Writing %1...").arg(m_patchFile->fileName())); + QStringList args; + args << QLatin1String("format-patch") << QLatin1String("-1") + << QLatin1String("--stdout") << QLatin1String("FETCH_HEAD"); + VcsBase::VcsBaseOutputWindow::instance()->appendCommand(m_repository, m_git, args); + if (debug) + qDebug() << m_git << args; + m_process.start(m_git, args); + m_process.closeWriteChannel(); +} + +void FetchContext::startCherryPick() +{ + VcsBase::VcsBaseOutputWindow::instance()->popup(); // Point user to errors. + VcsBase::VcsBaseOutputWindow::instance()->append(tr("Cherry-picking %1...").arg(m_patchFileName)); + QStringList args; + args << QLatin1String("cherry-pick") << QLatin1String("FETCH_HEAD"); + VcsBase::VcsBaseOutputWindow::instance()->appendCommand(m_repository, m_git, args); + if (debug) + qDebug() << m_git << args; + m_process.start(m_git, args); + m_process.closeWriteChannel(); +} + +void FetchContext::startCheckout() +{ + QStringList args; + args << QLatin1String("checkout") << QLatin1String("FETCH_HEAD"); + VcsBase::VcsBaseOutputWindow::instance()->appendCommand(m_repository, m_git, args); + m_process.start(m_git, args); + m_process.closeWriteChannel(); +} + +GerritPlugin::GerritPlugin(QObject *parent) + : QObject(parent) + , m_parameters(new GerritParameters) +{ +} + +GerritPlugin::~GerritPlugin() +{ +} + +bool GerritPlugin::initialize(Core::ActionContainer *ac) +{ + m_parameters->fromSettings(Core::ICore::instance()->settings()); + + Core::ActionManager *am = Core::ICore::instance()->actionManager(); + QAction *openViewAction = new QAction(tr("Gerrit..."), this); + + Core::Command *command = + am->registerAction(openViewAction, Constants::GERRIT_OPEN_VIEW, + Core::Context(Core::Constants::C_GLOBAL)); + connect(openViewAction, SIGNAL(triggered()), this, SLOT(openView())); + ac->addAction(command); + + Git::Internal::GitPlugin::instance()->addAutoReleasedObject(new GerritOptionsPage(m_parameters)); + return true; +} + +// Open or raise the Gerrit dialog window. +void GerritPlugin::openView() +{ + if (m_dialog.isNull()) { + while (!m_parameters->isValid()) { + const QString group = QLatin1String(VcsBase::Constants::VCS_SETTINGS_CATEGORY); + if (!Core::ICore::instance()->showOptionsDialog(group, GerritOptionsPage::optionsId())) + return; + } + GerritDialog *gd = new GerritDialog(m_parameters, Core::ICore::mainWindow()); + gd->setModal(false); + connect(gd, SIGNAL(fetchDisplay(QSharedPointer<Gerrit::Internal::GerritChange>)), + this, SLOT(fetchDisplay(QSharedPointer<Gerrit::Internal::GerritChange>))); + connect(gd, SIGNAL(fetchApply(QSharedPointer<Gerrit::Internal::GerritChange>)), + this, SLOT(fetchApply(QSharedPointer<Gerrit::Internal::GerritChange>))); + connect(gd, SIGNAL(fetchCheckout(QSharedPointer<Gerrit::Internal::GerritChange>)), + this, SLOT(fetchCheckout(QSharedPointer<Gerrit::Internal::GerritChange>))); + m_dialog = gd; + } + const Qt::WindowStates state = m_dialog.data()->windowState(); + if (state & Qt::WindowMinimized) + m_dialog.data()->setWindowState(state & ~Qt::WindowMinimized); + m_dialog.data()->show(); + m_dialog.data()->raise(); +} + +QString GerritPlugin::gitBinary() +{ + bool ok; + const QString git = Git::Internal::GitPlugin::instance()->gitClient()->gitBinaryPath(&ok); + if (!ok) { + VcsBase::VcsBaseOutputWindow::instance()->appendError(tr("git is not available.")); + return QString(); + } + return git; +} + +// Find the branch of a repository. +QString GerritPlugin::branch(const QString &repository) +{ + Git::Internal::GitClient *client = Git::Internal::GitPlugin::instance()->gitClient(); + QString errorMessage; + QString output; + if (client->synchronousBranchCmd(repository, QStringList(), &output, &errorMessage)) { + output.remove(QLatin1Char('\r')); + foreach (const QString &line, output.split(QLatin1Char('\n'))) + if (line.startsWith(QLatin1String("* "))) + return line.right(line.size() - 2); + } + return QString(); +} + +void GerritPlugin::fetchDisplay(const QSharedPointer<Gerrit::Internal::GerritChange> &change) +{ + fetch(change, FetchDisplay); +} + +void GerritPlugin::fetchApply(const QSharedPointer<Gerrit::Internal::GerritChange> &change) +{ + fetch(change, FetchApply); +} + +void GerritPlugin::fetchCheckout(const QSharedPointer<Gerrit::Internal::GerritChange> &change) +{ + fetch(change, FetchCheckout); +} + +void GerritPlugin::fetch(const QSharedPointer<Gerrit::Internal::GerritChange> &change, int mode) +{ + // Locate git. + const QString git = gitBinary(); + if (git.isEmpty()) + return; + + // Ask the user for a repository to retrieve the change. + const QString title = + tr("Enter Local Repository for '%1' (%2)").arg(change->project, change->branch); + const QString suggestedRespository = + findLocalRepository(change->project, change->branch); + const QString repository = + QFileDialog::getExistingDirectory(m_dialog.data(), + title, suggestedRespository); + if (repository.isEmpty()) + return; + + FetchContext *fc = new FetchContext(change, repository, git, + m_parameters, FetchMode(mode), this); + fc->start(); +} + +// Try to find a matching repository for a project by asking the VcsManager. +QString GerritPlugin::findLocalRepository(QString project, const QString &branch) const +{ + const Core::VcsManager *vcsManager = Core::ICore::instance()->vcsManager(); + const QStringList gitRepositories = vcsManager->repositories(Git::Internal::GitPlugin::instance()->gitVersionControl()); + // Determine key (file name) to look for (qt/qtbase->'qtbase'). + const int slashPos = project.lastIndexOf(QLatin1Char('/')); + if (slashPos != -1) + project.remove(0, slashPos + 1); + // When looking at branch 1.7, try to check folders + // "qtbase_17", 'qtbase1.7' with a semi-smart regular expression. + QScopedPointer<QRegExp> branchRegexp; + if (!branch.isEmpty() && branch != QLatin1String("master")) { + QString branchPattern = branch; + branchPattern.replace(QLatin1String("."), QLatin1String("[\\.-_]?")); + const QString pattern = QLatin1Char('^') + project + + QLatin1String("[-_]?") + + branchPattern + QLatin1Char('$'); + branchRegexp.reset(new QRegExp(pattern)); + if (!branchRegexp->isValid()) + branchRegexp.reset(); // Oops. + } + foreach (const QString &repository, gitRepositories) { + const QString fileName = QFileInfo(repository).fileName(); + if ((!branchRegexp.isNull() && branchRegexp->exactMatch(fileName)) + || fileName == project) { + // 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 (Core::DocumentManager::useProjectsDirectory()) + return Core::DocumentManager::projectsDirectory(); + + return QDir::currentPath(); +} + +} // namespace Internal +} // namespace Gerrit + +#include "gerritplugin.moc" |