Files
qt-creator/src/plugins/vcsbase/vcsbaseclient.cpp
cerf eed4d1e149 vcsbase: make settings mutable inside VCSBaseClient
VCSBaseClient takes now a pointer to VCSBaseClientSettings, so settings
can be changed within the VCS client. For example diff settings can now
be loaded and saved from within the VCS client.
This impacts the Bazaar and Mercurial plugins

Change-Id: I84882b1f3355e0ca2597704f48f589dca42fd661
Merge-request: 344
Reviewed-by: Tobias Hunger <tobias.hunger@nokia.com>
Reviewed-on: http://codereview.qt.nokia.com/452
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
2011-06-10 16:03:51 +02:00

515 lines
20 KiB
C++

/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Brian McGillion & Hugues Delorme
**
** Contact: Nokia Corporation (info@qt.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 info@qt.nokia.com.
**
**************************************************************************/
#include "vcsbaseclient.h"
#include "vcsjobrunner.h"
#include "vcsbaseclientsettings.h"
#include <QtDebug>
#include <coreplugin/icore.h>
#include <coreplugin/editormanager/editormanager.h>
#include <utils/qtcassert.h>
#include <utils/synchronousprocess.h>
#include <vcsbase/vcsbaseeditor.h>
#include <vcsbase/vcsbaseoutputwindow.h>
#include <vcsbase/vcsbaseplugin.h>
#include <QtCore/QStringList>
#include <QtCore/QSharedPointer>
#include <QtCore/QDir>
#include <QtCore/QProcess>
#include <QtCore/QTextCodec>
#include <QtCore/QtDebug>
#include <QtCore/QFileInfo>
#include <QtCore/QByteArray>
#include <QtCore/QMetaType>
/*!
\class VCSBase::VCSBaseClient
\brief Base class for Mercurial and Bazaar 'clients'.
Provides base functionality for common commands (diff, log, etc).
\sa VCSBase::VCSJobRunner
*/
Q_DECLARE_METATYPE(QVariant)
inline Core::IEditor *locateEditor(const Core::ICore *core, const char *property, const QString &entry)
{
foreach (Core::IEditor *ed, core->editorManager()->openedEditors())
if (ed->file()->property(property).toString() == entry)
return ed;
return 0;
}
namespace VCSBase {
class VCSBaseClientPrivate
{
public:
explicit VCSBaseClientPrivate(VCSBaseClientSettings *settings);
VCSJobRunner *m_jobManager;
Core::ICore *m_core;
VCSBaseClientSettings* m_clientSettings;
};
VCSBaseClientPrivate::VCSBaseClientPrivate(VCSBaseClientSettings *settings) :
m_jobManager(0), m_core(Core::ICore::instance()), m_clientSettings(settings)
{
}
VCSBaseClient::VCSBaseClient(VCSBaseClientSettings *settings) :
d(new VCSBaseClientPrivate(settings))
{
qRegisterMetaType<QVariant>();
}
VCSBaseClient::~VCSBaseClient()
{
if (d->m_jobManager) {
delete d->m_jobManager;
d->m_jobManager = 0;
}
}
bool VCSBaseClient::synchronousCreateRepository(const QString &workingDirectory)
{
const QStringList args(vcsCommandString(CreateRepositoryCommand));
QByteArray outputData;
if (!vcsFullySynchronousExec(workingDirectory, args, &outputData))
return false;
QString output = QString::fromLocal8Bit(outputData);
output.remove(QLatin1Char('\r'));
VCSBase::VCSBaseOutputWindow::instance()->append(output);
return true;
}
bool VCSBaseClient::synchronousClone(const QString &workingDir,
const QString &srcLocation,
const QString &dstLocation,
const QStringList &extraOptions)
{
QStringList args;
args << vcsCommandString(CloneCommand)
<< cloneArguments(srcLocation, dstLocation, extraOptions);
QByteArray stdOut;
return vcsFullySynchronousExec(workingDir, args, &stdOut);
}
bool VCSBaseClient::synchronousAdd(const QString &workingDir, const QString &filename)
{
QStringList args;
args << vcsCommandString(AddCommand) << filename;
QByteArray stdOut;
return vcsFullySynchronousExec(workingDir, args, &stdOut);
}
bool VCSBaseClient::synchronousRemove(const QString &workingDir, const QString &filename)
{
QStringList args;
args << vcsCommandString(RemoveCommand) << filename;
QByteArray stdOut;
return vcsFullySynchronousExec(workingDir, args, &stdOut);
}
bool VCSBaseClient::synchronousMove(const QString &workingDir,
const QString &from, const QString &to)
{
QStringList args;
args << vcsCommandString(MoveCommand) << from << to;
QByteArray stdOut;
return vcsFullySynchronousExec(workingDir, args, &stdOut);
}
bool VCSBaseClient::synchronousPull(const QString &workingDir,
const QString &srcLocation,
const QStringList &extraOptions)
{
QStringList args;
args << vcsCommandString(PullCommand) << pullArguments(srcLocation, extraOptions);
// Disable UNIX terminals to suppress SSH prompting
const unsigned flags =
VCSBase::VCSBasePlugin::SshPasswordPrompt
| VCSBase::VCSBasePlugin::ShowStdOutInLogWindow
| VCSBase::VCSBasePlugin::ShowSuccessMessage;
const Utils::SynchronousProcessResponse resp = vcsSynchronousExec(workingDir, args, flags);
const bool ok = resp.result == Utils::SynchronousProcessResponse::Finished;
if (ok)
emit changed(QVariant(workingDir));
return ok;
}
bool VCSBaseClient::synchronousPush(const QString &workingDir,
const QString &dstLocation,
const QStringList &extraOptions)
{
QStringList args;
args << vcsCommandString(PushCommand) << pushArguments(dstLocation, extraOptions);
// Disable UNIX terminals to suppress SSH prompting
const unsigned flags =
VCSBase::VCSBasePlugin::SshPasswordPrompt
| VCSBase::VCSBasePlugin::ShowStdOutInLogWindow
| VCSBase::VCSBasePlugin::ShowSuccessMessage;
const Utils::SynchronousProcessResponse resp = vcsSynchronousExec(workingDir, args, flags);
return resp.result == Utils::SynchronousProcessResponse::Finished;
}
bool VCSBaseClient::vcsFullySynchronousExec(const QString &workingDir,
const QStringList &args,
QByteArray *output)
{
QProcess vcsProcess;
if (!workingDir.isEmpty())
vcsProcess.setWorkingDirectory(workingDir);
VCSJobRunner::setProcessEnvironment(&vcsProcess);
const QString binary = settings()->binary();
const QStringList arguments = settings()->standardArguments() + args;
VCSBase::VCSBaseOutputWindow *outputWindow = VCSBase::VCSBaseOutputWindow::instance();
outputWindow->appendCommand(workingDir, binary, args);
vcsProcess.start(binary, arguments);
if (!vcsProcess.waitForStarted()) {
outputWindow->appendError(VCSJobRunner::msgStartFailed(binary, vcsProcess.errorString()));
return false;
}
vcsProcess.closeWriteChannel();
QByteArray stdErr;
if (!Utils::SynchronousProcess::readDataFromProcess(vcsProcess, settings()->timeoutMilliSeconds(),
output, &stdErr, true)) {
Utils::SynchronousProcess::stopProcess(vcsProcess);
outputWindow->appendError(VCSJobRunner::msgTimeout(binary, settings()->timeoutSeconds()));
return false;
}
if (!stdErr.isEmpty())
outputWindow->append(QString::fromLocal8Bit(stdErr));
return vcsProcess.exitStatus() == QProcess::NormalExit && vcsProcess.exitCode() == 0;
}
Utils::SynchronousProcessResponse VCSBaseClient::vcsSynchronousExec(
const QString &workingDirectory,
const QStringList &args,
unsigned flags,
QTextCodec *outputCodec)
{
const QString binary = settings()->binary();
const QStringList arguments = settings()->standardArguments() + args;
return VCSBase::VCSBasePlugin::runVCS(workingDirectory, binary, arguments,
settings()->timeoutMilliSeconds(),
flags, outputCodec);
}
void VCSBaseClient::slotAnnotateRevisionRequested(const QString &source,
QString change,
int lineNumber)
{
// This might be invoked with a verbose revision description
// "SHA1 author subject" from the annotation context menu. Strip the rest.
const int blankPos = change.indexOf(QLatin1Char(' '));
if (blankPos != -1)
change.truncate(blankPos);
const QFileInfo fi(source);
annotate(fi.absolutePath(), fi.fileName(), change, lineNumber);
}
void VCSBaseClient::annotate(const QString &workingDir, const QString &file,
const QString revision /* = QString() */,
int lineNumber /* = -1 */)
{
Q_UNUSED(lineNumber)
const QString vcsCmdString = vcsCommandString(AnnotateCommand);
QStringList args;
args << vcsCmdString << annotateArguments(file, revision, lineNumber);
const QString kind = vcsEditorKind(AnnotateCommand);
const QString id = VCSBase::VCSBaseEditorWidget::getSource(workingDir, QStringList(file));
const QString title = vcsEditorTitle(vcsCmdString, id);
const QString source = VCSBase::VCSBaseEditorWidget::getSource(workingDir, file);
VCSBase::VCSBaseEditorWidget *editor = createVCSEditor(kind, title, source, true,
vcsCmdString.toLatin1().constData(), id);
QSharedPointer<VCSJob> job(new VCSJob(workingDir, args, editor));
enqueueJob(job);
}
void VCSBaseClient::diff(const QString &workingDir, const QStringList &files,
const QStringList &extraOptions)
{
const QString vcsCmdString = vcsCommandString(DiffCommand);
QStringList args;
args << vcsCmdString << diffArguments(files, extraOptions);
const QString kind = vcsEditorKind(DiffCommand);
const QString id = VCSBase::VCSBaseEditorWidget::getTitleId(workingDir, files);
const QString title = vcsEditorTitle(vcsCmdString, id);
const QString source = VCSBase::VCSBaseEditorWidget::getSource(workingDir, files);
VCSBase::VCSBaseEditorWidget *editor = createVCSEditor(kind, title, source, true,
vcsCmdString.toLatin1().constData(), id);
editor->setDiffBaseDirectory(workingDir);
initializeDiffEditor(workingDir, files, extraOptions, editor);
QSharedPointer<VCSJob> job(new VCSJob(workingDir, args, editor));
enqueueJob(job);
}
void VCSBaseClient::log(const QString &workingDir, const QStringList &files,
const QStringList &extraOptions,
bool enableAnnotationContextMenu)
{
const QString vcsCmdString = vcsCommandString(LogCommand);
QStringList args;
args << vcsCmdString << logArguments(files, extraOptions);
const QString kind = vcsEditorKind(LogCommand);
const QString id = VCSBase::VCSBaseEditorWidget::getTitleId(workingDir, files);
const QString title = vcsEditorTitle(vcsCmdString, id);
const QString source = VCSBase::VCSBaseEditorWidget::getSource(workingDir, files);
VCSBase::VCSBaseEditorWidget *editor = createVCSEditor(kind, title, source, true,
vcsCmdString.toLatin1().constData(), id);
editor->setFileLogAnnotateEnabled(enableAnnotationContextMenu);
QSharedPointer<VCSJob> job(new VCSJob(workingDir, args, editor));
enqueueJob(job);
}
void VCSBaseClient::revertFile(const QString &workingDir,
const QString &file,
const QString &revision)
{
QStringList args(vcsCommandString(RevertCommand));
args << revertArguments(file, revision);
// Indicate repository change or file list
QSharedPointer<VCSJob> job(new VCSJob(workingDir, args));
job->setCookie(QStringList(workingDir + QLatin1Char('/') + file));
connect(job.data(), SIGNAL(succeeded(QVariant)),
this, SIGNAL(changed(QVariant)), Qt::QueuedConnection);
enqueueJob(job);
}
void VCSBaseClient::revertAll(const QString &workingDir, const QString &revision)
{
QStringList args(vcsCommandString(RevertCommand));
args << revertAllArguments(revision);
// Indicate repository change or file list
QSharedPointer<VCSJob> job(new VCSJob(workingDir, args));
connect(job.data(), SIGNAL(succeeded(QVariant)),
this, SIGNAL(changed(QVariant)), Qt::QueuedConnection);
enqueueJob(job);
}
void VCSBaseClient::status(const QString &workingDir, const QString &file)
{
QStringList args(vcsCommandString(StatusCommand));
args << statusArguments(file);
VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
outwin->setRepository(workingDir);
QSharedPointer<VCSJob> job(new VCSJob(workingDir, args));
connect(job.data(), SIGNAL(succeeded(QVariant)),
outwin, SLOT(clearRepository()), Qt::QueuedConnection);
enqueueJob(job);
}
void VCSBaseClient::statusWithSignal(const QString &repositoryRoot)
{
QStringList args(vcsCommandString(StatusCommand));
args << statusArguments(QString());
QSharedPointer<VCSJob> job(new VCSJob(repositoryRoot, args, VCSJob::RawDataEmitMode));
connect(job.data(), SIGNAL(rawData(QByteArray)),
this, SLOT(statusParser(QByteArray)));
enqueueJob(job);
}
QString VCSBaseClient::vcsCommandString(VCSCommand cmd) const
{
switch (cmd) {
case CreateRepositoryCommand: return QLatin1String("init");
case CloneCommand: return QLatin1String("clone");
case AddCommand: return QLatin1String("add");
case RemoveCommand: return QLatin1String("remove");
case MoveCommand: return QLatin1String("rename");
case PullCommand: return QLatin1String("pull");
case PushCommand: return QLatin1String("push");
case CommitCommand: return QLatin1String("commit");
case ImportCommand: return QLatin1String("import");
case UpdateCommand: return QLatin1String("update");
case RevertCommand: return QLatin1String("revert");
case AnnotateCommand: return QLatin1String("annotate");
case DiffCommand: return QLatin1String("diff");
case LogCommand: return QLatin1String("log");
case StatusCommand: return QLatin1String("status");
}
return QString();
}
void VCSBaseClient::statusParser(const QByteArray &data)
{
QList<QPair<QString, QString> > statusList;
QStringList rawStatusList = QTextCodec::codecForLocale()->toUnicode(data).split(QLatin1Char('\n'));
foreach (const QString &string, rawStatusList) {
QPair<QString, QString> status = parseStatusLine(string);
if (!status.first.isEmpty() && !status.second.isEmpty())
statusList.append(status);
}
emit parsedStatus(statusList);
}
void VCSBaseClient::import(const QString &repositoryRoot, const QStringList &files)
{
QStringList args(vcsCommandString(ImportCommand));
args << importArguments(files);
QSharedPointer<VCSJob> job(new VCSJob(repositoryRoot, args));
enqueueJob(job);
}
void VCSBaseClient::view(const QString &source, const QString &id)
{
QStringList args(viewArguments(id));
const QString kind = vcsEditorKind(DiffCommand);
const QString title = vcsEditorTitle(vcsCommandString(LogCommand), id);
VCSBase::VCSBaseEditorWidget *editor = createVCSEditor(kind, title, source,
true, "view", id);
const QFileInfo fi(source);
const QString workingDirPath = fi.isFile() ? fi.absolutePath() : source;
QSharedPointer<VCSJob> job(new VCSJob(workingDirPath, args, editor));
enqueueJob(job);
}
void VCSBaseClient::update(const QString &repositoryRoot, const QString &revision)
{
QStringList args(vcsCommandString(UpdateCommand));
args.append(updateArguments(revision));
QSharedPointer<VCSJob> job(new VCSJob(repositoryRoot, args));
job->setCookie(repositoryRoot);
// Suppress SSH prompting
job->setUnixTerminalDisabled(VCSBase::VCSBasePlugin::isSshPromptConfigured());
connect(job.data(), SIGNAL(succeeded(QVariant)), this, SIGNAL(changed(QVariant)), Qt::QueuedConnection);
enqueueJob(job);
}
void VCSBaseClient::commit(const QString &repositoryRoot,
const QStringList &files,
const QString &commitMessageFile,
const QStringList &extraOptions)
{
QStringList args(vcsCommandString(CommitCommand));
args.append(commitArguments(files, commitMessageFile, extraOptions));
QSharedPointer<VCSJob> job(new VCSJob(repositoryRoot, args));
enqueueJob(job);
}
VCSBaseClientSettings *VCSBaseClient::settings() const
{
return d->m_clientSettings;
}
void VCSBaseClient::settingsChanged()
{
if (d->m_jobManager) {
d->m_jobManager->setSettings(settings()->binary(),
settings()->standardArguments(),
settings()->timeoutMilliSeconds());
d->m_jobManager->restart();
}
}
void VCSBaseClient::initializeDiffEditor(const QString & /* workingDir */, const QStringList & /* files */,
const QStringList & /* extraOptions */,
VCSBaseEditorWidget *)
{
}
QString VCSBaseClient::vcsEditorTitle(const QString &vcsCmd, const QString &sourceId) const
{
return QFileInfo(settings()->binary()).baseName() +
QLatin1Char(' ') + vcsCmd + QLatin1Char(' ') +
QFileInfo(sourceId).fileName();
}
VCSBase::VCSBaseEditorWidget *VCSBaseClient::createVCSEditor(const QString &kind, QString title,
const QString &source, bool setSourceCodec,
const char *registerDynamicProperty,
const QString &dynamicPropertyValue) const
{
VCSBase::VCSBaseEditorWidget *baseEditor = 0;
Core::IEditor *outputEditor = locateEditor(d->m_core, registerDynamicProperty, dynamicPropertyValue);
const QString progressMsg = tr("Working...");
if (outputEditor) {
// Exists already
outputEditor->createNew(progressMsg);
baseEditor = VCSBase::VCSBaseEditorWidget::getVcsBaseEditor(outputEditor);
QTC_ASSERT(baseEditor, return 0);
} else {
outputEditor = d->m_core->editorManager()->openEditorWithContents(kind, &title, progressMsg);
outputEditor->file()->setProperty(registerDynamicProperty, dynamicPropertyValue);
baseEditor = VCSBase::VCSBaseEditorWidget::getVcsBaseEditor(outputEditor);
connect(baseEditor, SIGNAL(annotateRevisionRequested(QString,QString,int)),
this, SLOT(slotAnnotateRevisionRequested(QString,QString,int)));
QTC_ASSERT(baseEditor, return 0);
baseEditor->setSource(source);
if (setSourceCodec)
baseEditor->setCodec(VCSBase::VCSBaseEditorWidget::getCodec(source));
}
baseEditor->setForceReadOnly(true);
d->m_core->editorManager()->activateEditor(outputEditor, Core::EditorManager::ModeSwitch);
return baseEditor;
}
void VCSBaseClient::enqueueJob(const QSharedPointer<VCSJob> &job)
{
if (!d->m_jobManager) {
d->m_jobManager = new VCSJobRunner();
d->m_jobManager->setSettings(settings()->binary(),
settings()->standardArguments(),
settings()->timeoutMilliSeconds());
d->m_jobManager->start();
}
d->m_jobManager->enqueueJob(job);
}
} // namespace VCSBase