summaryrefslogtreecommitdiff
path: root/src/plugins/git/gerrit/gerritplugin.cpp
diff options
context:
space:
mode:
authorFriedemann Kleint <Friedemann.Kleint@nokia.com>2012-04-20 17:47:37 +0200
committerFriedemann Kleint <Friedemann.Kleint@nokia.com>2012-04-26 14:34:41 +0200
commit2193915cca82693abd0cbe69670dfee14212b6bf (patch)
tree29e9c19219898e429ec281676ce8dfd049e0d5a3 /src/plugins/git/gerrit/gerritplugin.cpp
parent2b88fedc472543700e76f965a93d6110094ff59b (diff)
downloadqt-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.cpp488
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"