diff --git a/src/libs/utils/synchronousprocess.cpp b/src/libs/utils/synchronousprocess.cpp index 9486caa5a0125536b2434c39ae95f766c36f944f..6a2b365202143d4994a966599ca096e5e1efd883 100644 --- a/src/libs/utils/synchronousprocess.cpp +++ b/src/libs/utils/synchronousprocess.cpp @@ -220,6 +220,16 @@ void SynchronousProcess::setEnvironment(const QStringList &e) m_d->m_process.setEnvironment(e); } +void SynchronousProcess::setProcessEnvironment(const QProcessEnvironment &environment) +{ + m_d->m_process.setProcessEnvironment(environment); +} + +QProcessEnvironment SynchronousProcess::processEnvironment() const +{ + return m_d->m_process.processEnvironment(); +} + void SynchronousProcess::setWorkingDirectory(const QString &workingDirectory) { m_d->m_process.setWorkingDirectory(workingDirectory); diff --git a/src/libs/utils/synchronousprocess.h b/src/libs/utils/synchronousprocess.h index 86f45060cd0a64c663d6c57613502d02e2ac8a6d..8e7aa042f4a9d8fa94ab6b26b4b84381f1f01d8b 100644 --- a/src/libs/utils/synchronousprocess.h +++ b/src/libs/utils/synchronousprocess.h @@ -110,6 +110,9 @@ public: QStringList environment() const; void setEnvironment(const QStringList &); + void setProcessEnvironment(const QProcessEnvironment &environment); + QProcessEnvironment processEnvironment() const; + void setWorkingDirectory(const QString &workingDirectory); QString workingDirectory() const; diff --git a/src/plugins/coreplugin/actionmanager/command.cpp b/src/plugins/coreplugin/actionmanager/command.cpp index bbf720051b0f8ab60ae1d8a2a8552803e9170b40..653009203b78866386d24fed6c1eab8c60f6c7c2 100644 --- a/src/plugins/coreplugin/actionmanager/command.cpp +++ b/src/plugins/coreplugin/actionmanager/command.cpp @@ -27,12 +27,17 @@ ** **************************************************************************/ +#include "command_p.h" + +#include "icore.h" +#include "uniqueidmanager.h" + #include <QtCore/QDebug> +#include <QtCore/QTextStream> + #include <QtGui/QAction> #include <QtGui/QShortcut> -#include "command_p.h" - /*! \class Core::Command \mainclass @@ -467,6 +472,20 @@ bool OverrideableAction::setCurrentContext(const QList<int> &context) return false; } +static inline QString msgActionWarning(QAction *newAction, int k, QAction *oldAction) +{ + QString msg; + QTextStream str(&msg); + str << "addOverrideAction " << newAction->objectName() << '/' << newAction->text() + << ": Action "; + if (oldAction) + str << oldAction->objectName() << '/' << oldAction->text(); + str << " is already registered for context " << k << ' ' + << Core::ICore::instance()->uniqueIDManager()->stringForUniqueIdentifier(k) + << '.'; + return msg; +} + void OverrideableAction::addOverrideAction(QAction *action, const QList<int> &context) { if (context.isEmpty()) { @@ -475,7 +494,7 @@ void OverrideableAction::addOverrideAction(QAction *action, const QList<int> &co for (int i=0; i<context.size(); ++i) { int k = context.at(i); if (m_contextActionMap.contains(k)) - qWarning() << QString("addOverrideAction: action already registered for context when registering '%1'").arg(action->text()); + qWarning("%s", qPrintable(msgActionWarning(action, k, m_contextActionMap.value(k, 0)))); m_contextActionMap.insert(k, action); } } diff --git a/src/plugins/perforce/pendingchangesdialog.ui b/src/plugins/perforce/pendingchangesdialog.ui index bcee662a027917baab0127b7493b80bd9198dc11..0cae43326eeb8c8c7598809aa682a105608935af 100644 --- a/src/plugins/perforce/pendingchangesdialog.ui +++ b/src/plugins/perforce/pendingchangesdialog.ui @@ -1,41 +1,34 @@ -<ui version="4.0" > +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> <class>Perforce::Internal::PendingChangesDialog</class> - <widget class="QDialog" name="Perforce::Internal::PendingChangesDialog" > - <property name="geometry" > - <rect> - <x>0</x> - <y>0</y> - <width>333</width> - <height>126</height> - </rect> - </property> - <property name="windowTitle" > + <widget class="QDialog" name="Perforce::Internal::PendingChangesDialog"> + <property name="windowTitle"> <string>P4 Pending Changes</string> </property> - <layout class="QVBoxLayout" > - <property name="margin" > - <number>9</number> - </property> - <property name="spacing" > + <layout class="QVBoxLayout"> + <property name="spacing"> <number>6</number> </property> + <property name="margin"> + <number>9</number> + </property> <item> - <widget class="QListWidget" name="listWidget" /> + <widget class="QListWidget" name="listWidget"/> </item> <item> - <layout class="QHBoxLayout" > - <property name="margin" > - <number>0</number> - </property> - <property name="spacing" > + <layout class="QHBoxLayout"> + <property name="spacing"> <number>6</number> </property> + <property name="margin"> + <number>0</number> + </property> <item> <spacer> - <property name="orientation" > + <property name="orientation"> <enum>Qt::Horizontal</enum> </property> - <property name="sizeHint" > + <property name="sizeHint" stdset="0"> <size> <width>131</width> <height>31</height> @@ -44,15 +37,15 @@ </spacer> </item> <item> - <widget class="QPushButton" name="submitButton" > - <property name="text" > + <widget class="QPushButton" name="submitButton"> + <property name="text"> <string>Submit</string> </property> </widget> </item> <item> - <widget class="QPushButton" name="cancelButton" > - <property name="text" > + <widget class="QPushButton" name="cancelButton"> + <property name="text"> <string>Cancel</string> </property> </widget> @@ -69,11 +62,11 @@ <receiver>Perforce::Internal::PendingChangesDialog</receiver> <slot>accept()</slot> <hints> - <hint type="sourcelabel" > + <hint type="sourcelabel"> <x>278</x> <y>253</y> </hint> - <hint type="destinationlabel" > + <hint type="destinationlabel"> <x>96</x> <y>254</y> </hint> @@ -85,11 +78,11 @@ <receiver>Perforce::Internal::PendingChangesDialog</receiver> <slot>reject()</slot> <hints> - <hint type="sourcelabel" > + <hint type="sourcelabel"> <x>369</x> <y>253</y> </hint> - <hint type="destinationlabel" > + <hint type="destinationlabel"> <x>179</x> <y>282</y> </hint> diff --git a/src/plugins/perforce/perforce.pro b/src/plugins/perforce/perforce.pro index e93e3378d85a680250174a225e786ce8781cafa3..27d398f0f32641ed2aba33a4a67c8c3b80207739 100644 --- a/src/plugins/perforce/perforce.pro +++ b/src/plugins/perforce/perforce.pro @@ -6,6 +6,7 @@ include(perforce_dependencies.pri) HEADERS += \ perforceplugin.h \ + perforcechecker.h \ settingspage.h \ perforceeditor.h \ changenumberdialog.h \ @@ -18,6 +19,7 @@ HEADERS += \ perforcesubmiteditorwidget.h SOURCES += perforceplugin.cpp \ + perforcechecker.cpp \ settingspage.cpp \ perforceeditor.cpp \ changenumberdialog.cpp \ diff --git a/src/plugins/perforce/perforcechecker.cpp b/src/plugins/perforce/perforcechecker.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1039e954797a93e38170262c1aa1add7003b0da3 --- /dev/null +++ b/src/plugins/perforce/perforcechecker.cpp @@ -0,0 +1,217 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#include "perforcechecker.h" + +#include <utils/qtcassert.h> + +#include <QtCore/QRegExp> +#include <QtCore/QTimer> +#include <QtCore/QFileInfo> + +#include <QtGui/QApplication> +#include <QtGui/QCursor> + +namespace Perforce { +namespace Internal { + +PerforceChecker::PerforceChecker(QObject *parent) : + QObject(parent), + m_timeOutMS(-1), + m_timedOut(false), + m_useOverideCursor(false), + m_isOverrideCursor(false) +{ + connect(&m_process, SIGNAL(error(QProcess::ProcessError)), + this, SLOT(slotError(QProcess::ProcessError))); + connect(&m_process, SIGNAL(finished(int,QProcess::ExitStatus)), + this, SLOT(slotFinished(int,QProcess::ExitStatus))); +} + +PerforceChecker::~PerforceChecker() +{ + resetOverrideCursor(); +} + +bool PerforceChecker::isRunning() const +{ + return m_process.state() == QProcess::Running; +} + +void PerforceChecker::resetOverrideCursor() +{ + if (m_isOverrideCursor) { + QApplication::restoreOverrideCursor(); + m_isOverrideCursor = false; + } +} + +void PerforceChecker::start(const QString &binary, + const QStringList &basicArgs, + int timeoutMS) +{ + if (isRunning()) { + emitFailed(QLatin1String("Internal error: process still running")); + return; + } + if (binary.isEmpty()) { + emitFailed(tr("No executable specified")); + return; + } + m_binary = binary; + QStringList args = basicArgs; + args << QLatin1String("client") << QLatin1String("-o"); + m_process.start(m_binary, args); + m_process.closeWriteChannel(); + // Timeout handling + m_timeOutMS = timeoutMS; + m_timedOut = false; + if (timeoutMS > 0) + QTimer::singleShot(m_timeOutMS, this, SLOT(slotTimeOut())); + // Cursor + if (m_useOverideCursor) { + m_isOverrideCursor = true; + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + } +} + +bool PerforceChecker::ensureProcessStopped(QProcess &p) +{ + if (p.state() != QProcess::Running) + return true; + p.terminate(); + if (p.waitForFinished(300)) + return true; + p.kill(); + return p.waitForFinished(300); +} + +void PerforceChecker::slotTimeOut() +{ + if (!isRunning()) + return; + m_timedOut = true; + ensureProcessStopped(m_process); + emitFailed(tr("\"%1\" timed out after %2ms.").arg(m_binary).arg(m_timeOutMS)); +} + +void PerforceChecker::slotError(QProcess::ProcessError error) +{ + if (m_timedOut) + return; + switch (error) { + case QProcess::FailedToStart: + emitFailed(tr("Unable to launch \"%1\": %2").arg(m_binary, m_process.errorString())); + break; + case QProcess::Crashed: // Handled elsewhere + case QProcess::Timedout: + break; + case QProcess::ReadError: + case QProcess::WriteError: + case QProcess::UnknownError: + ensureProcessStopped(m_process); + break; + } +} + +void PerforceChecker::slotFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (m_timedOut) + return; + switch (exitStatus) { + case QProcess::CrashExit: + emitFailed(tr("\"%1\" crashed.").arg(m_binary)); + break; + case QProcess::NormalExit: + if (exitCode) { + const QString stdErr = QString::fromLocal8Bit(m_process.readAllStandardError()); + emitFailed(tr("\"%1\" terminated with exit code %2: %3").arg(m_binary).arg(exitCode).arg(stdErr)); + } else { + parseOutput(QString::fromLocal8Bit(m_process.readAllStandardOutput())); + } + break; + } +} + +// Parse p4 client output for the top level +static inline QString clientRootFromOutput(const QString &in) +{ + QRegExp regExp(QLatin1String("(\\n|\\r\\n|\\r)Root:\\s*(.*)(\\n|\\r\\n|\\r)")); + QTC_ASSERT(regExp.isValid(), return QString()); + regExp.setMinimal(true); + if (regExp.indexIn(in) != -1) + return regExp.cap(2).trimmed(); + return QString(); +} + +void PerforceChecker::parseOutput(const QString &response) +{ + if (!response.contains(QLatin1String("View:")) && !response.contains(QLatin1String("//depot/"))) { + emitFailed(tr("The client does not seem to contain any mapped files.")); + return; + } + const QString repositoryRoot = clientRootFromOutput(response); + if (repositoryRoot.isEmpty()) { + emitFailed(tr("Unable to determine the client root.")); + return; + } + // Check existence. No precise check here, might be a symlink + const QFileInfo fi(repositoryRoot); + if (fi.exists()) { + emitSucceeded(repositoryRoot); + } else { + emitFailed(tr("The repository \"%1\" does not exist.").arg(repositoryRoot)); + } +} + +void PerforceChecker::emitFailed(const QString &m) +{ + resetOverrideCursor(); + emit failed(m); +} + +void PerforceChecker::emitSucceeded(const QString &m) +{ + resetOverrideCursor(); + emit succeeded(m); +} + +bool PerforceChecker::useOverideCursor() const +{ + return m_useOverideCursor; +} + +void PerforceChecker::setUseOverideCursor(bool v) +{ + m_useOverideCursor = v; +} + +} +} + diff --git a/src/plugins/perforce/perforcechecker.h b/src/plugins/perforce/perforcechecker.h new file mode 100644 index 0000000000000000000000000000000000000000..906ecd5c29d327b45096d45bb1b5f4963e5cf8ed --- /dev/null +++ b/src/plugins/perforce/perforcechecker.h @@ -0,0 +1,88 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** Commercial Usage +** +** Licensees holding valid Qt Commercial licenses may use this file in +** accordance with the Qt Commercial License Agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Nokia. +** +** GNU Lesser General Public License Usage +** +** Alternatively, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at http://qt.nokia.com/contact. +** +**************************************************************************/ + +#ifndef PERFORCECHECKER_H +#define PERFORCECHECKER_H + +#include <QtCore/QObject> +#include <QtCore/QProcess> + +namespace Perforce { +namespace Internal { + +// Perforce checker: Calls perforce asynchronously to do +// a check of the configuration and emits signals with the top level or +// an error message. + +class PerforceChecker : public QObject +{ + Q_OBJECT +public: + explicit PerforceChecker(QObject *parent = 0); + virtual ~PerforceChecker(); + +public slots: + void start(const QString &binary, + const QStringList &basicArgs = QStringList(), + int timeoutMS = -1); + + bool isRunning() const; + + bool useOverideCursor() const; + void setUseOverideCursor(bool v); + + static bool ensureProcessStopped(QProcess &p); + +signals: + void succeeded(const QString &repositoryRoot); + void failed(const QString &errorMessage); + +private slots: + void slotError(QProcess::ProcessError error); + void slotFinished(int exitCode, QProcess::ExitStatus exitStatus); + void slotTimeOut(); + +private: + void emitFailed(const QString &); + void emitSucceeded(const QString &); + void parseOutput(const QString &); + inline void resetOverrideCursor(); + + QProcess m_process; + QString m_binary; + int m_timeOutMS; + bool m_timedOut; + bool m_useOverideCursor; + bool m_isOverrideCursor; +}; + +} +} + +#endif // PERFORCECHECKER_H diff --git a/src/plugins/perforce/perforceconstants.h b/src/plugins/perforce/perforceconstants.h index 39d4031f98500db0c5744c5da77454a61af1ec8d..3402b49dc1591f5dd51e133244a1f96d9d282056 100644 --- a/src/plugins/perforce/perforceconstants.h +++ b/src/plugins/perforce/perforceconstants.h @@ -33,11 +33,24 @@ namespace Perforce { namespace Constants { -const char * const C_PERFORCEEDITOR = "Perforce Editor"; +const char * const PERFORCEEDITOR_CONTEXT = "Perforce Editor"; + + +const char * const PERFORCE_SUBMIT_EDITOR_KIND = "Perforce Submit Editor"; +const char * const PERFORCESUBMITEDITOR_CONTEXT = "Perforce Submit Editor"; + +const char * const PERFORCE_COMMANDLOG_EDITOR_KIND = "Perforce Command Log Editor"; +const char * const PERFORCE_COMMANDLOG_EDITOR_CONTEXT = "Perforce Command Log Editor"; + +const char * const PERFORCE_LOG_EDITOR_KIND = "Perforce Log Editor"; +const char * const PERFORCE_LOG_EDITOR_CONTEXT = "Perforce Log Editor"; + +const char * const PERFORCE_DIFF_EDITOR_KIND = "Perforce Diff Editor"; +const char * const PERFORCE_DIFF_EDITOR_CONTEXT = "Perforce Diff Editor"; + +const char * const PERFORCE_ANNOTATION_EDITOR_KIND = "Perforce Annotation Editor"; +const char * const PERFORCE_ANNOTATION_EDITOR_CONTEXT = "Perforce Annotation Editor"; -const char * const PERFORCEEDITOR_KIND = "Perforce Editor"; -const char * const C_PERFORCESUBMITEDITOR = "Perforce Submit Editor"; -const char * const PERFORCESUBMITEDITOR_KIND = "Perforce Submit Editor"; const char * const SUBMIT_CURRENT = "Perforce.SubmitCurrentLog"; const char * const DIFF_SELECTED = "Perforce.DiffSelectedFilesInLog"; const char * const SUBMIT_MIMETYPE = "application/vnd.nokia.text.p4.submit"; diff --git a/src/plugins/perforce/perforceplugin.cpp b/src/plugins/perforce/perforceplugin.cpp index 8e1e55ea73d4f44b8b492fec03ee738e9b6994b2..350c0ffb9bd75106104da838962a0f4d12bddae6 100644 --- a/src/plugins/perforce/perforceplugin.cpp +++ b/src/plugins/perforce/perforceplugin.cpp @@ -35,6 +35,7 @@ #include "perforceeditor.h" #include "perforcesubmiteditor.h" #include "perforceversioncontrol.h" +#include "perforcechecker.h" #include "settingspage.h" #include <coreplugin/actionmanager/actionmanager.h> @@ -52,8 +53,6 @@ #include <vcsbase/basevcssubmiteditorfactory.h> #include <vcsbase/vcsbaseeditor.h> #include <vcsbase/vcsbaseoutputwindow.h> -#include <projectexplorer/project.h> -#include <projectexplorer/session.h> #include <QtCore/QtPlugin> #include <QtCore/QDebug> @@ -69,30 +68,28 @@ #include <QtGui/QMenu> #include <QtGui/QMessageBox> -using namespace Perforce::Internal; - enum { p4Timeout = 20000 }; static const VCSBase::VCSBaseEditorParameters editorParameters[] = { { VCSBase::RegularCommandOutput, - "Perforce Command Log Editor", - Perforce::Constants::C_PERFORCEEDITOR, + Perforce::Constants::PERFORCE_COMMANDLOG_EDITOR_KIND, + Perforce::Constants::PERFORCE_COMMANDLOG_EDITOR_CONTEXT, "application/vnd.nokia.text.scs_commandlog", "scslog"}, { VCSBase::LogOutput, - "Perforce File Log Editor", - Perforce::Constants::C_PERFORCEEDITOR, + Perforce::Constants::PERFORCE_LOG_EDITOR_KIND, + Perforce::Constants::PERFORCE_LOG_EDITOR_CONTEXT, "application/vnd.nokia.text.scs_filelog", "scsfilelog"}, { VCSBase::AnnotateOutput, - "Perforce Annotation Editor", - Perforce::Constants::C_PERFORCEEDITOR, + Perforce::Constants::PERFORCE_ANNOTATION_EDITOR_KIND, + Perforce::Constants::PERFORCE_ANNOTATION_EDITOR_CONTEXT, "application/vnd.nokia.text.scs_annotation", "scsannotate"}, { VCSBase::DiffOutput, - "Perforce Diff Editor", - Perforce::Constants::C_PERFORCEEDITOR, + Perforce::Constants::PERFORCE_DIFF_EDITOR_KIND, + Perforce::Constants::PERFORCE_DIFF_EDITOR_CONTEXT, "text/x-patch","diff"} }; @@ -108,25 +105,29 @@ static inline QString debugCodec(const QTextCodec *c) return c ? QString::fromAscii(c->name()) : QString::fromAscii("Null codec"); } -// Return the project files relevant for VCS -static const QStringList currentProjectFiles(QString *name) -{ - QStringList files = VCSBase::VCSBaseSubmitEditor::currentProjectFiles(true, name); - if (!files.empty()) { - // Filter out mkspecs/qconfig.pri - QString exclusion = QLatin1String("mkspecs"); - exclusion += QDir::separator(); - exclusion += QLatin1String("qconfig.pri"); - for (QStringList::iterator it = files.begin(); it != files.end(); ) { - if (it->endsWith(exclusion)) { - it = files.erase(it); - break; - } else { - ++it; - } - } - } - return files; +// Ensure adding "..." to relative paths which is p4's convention +// for the current directory +static inline QStringList perforceRelativeFileArguments(const QStringList &args) +{ + if (args.isEmpty()) + return QStringList(QLatin1String("...")); + QTC_ASSERT(args.size() == 1, return QStringList()) + QStringList p4Args = args; + p4Args.front() += QLatin1String("/..."); + return p4Args; +} + +static inline QStringList perforceRelativeProjectDirectory(const VCSBase::VCSBasePluginState &s) +{ + return perforceRelativeFileArguments(s.relativeCurrentProject()); +} + +// Clean user setting off diff-binary for 'p4 resolve' and 'p4 diff'. +static inline QProcessEnvironment overrideDiffEnvironmentVariable() +{ + QProcessEnvironment rc = QProcessEnvironment::systemEnvironment(); + rc.remove(QLatin1String("P4DIFF")); + return rc; } static const char * const CMD_ID_PERFORCE_MENU = "Perforce.Menu"; @@ -138,6 +139,8 @@ static const char * const CMD_ID_REVERT = "Perforce.Revert"; static const char * const CMD_ID_DIFF_CURRENT = "Perforce.DiffCurrent"; static const char * const CMD_ID_DIFF_PROJECT = "Perforce.DiffProject"; static const char * const CMD_ID_UPDATE_PROJECT = "Perforce.UpdateProject"; +static const char * const CMD_ID_REVERT_PROJECT = "Perforce.RevertProject"; +static const char * const CMD_ID_REVERT_UNCHANGED_PROJECT = "Perforce.RevertUnchangedProject"; static const char * const CMD_ID_DIFF_ALL = "Perforce.DiffAll"; static const char * const CMD_ID_RESOLVE = "Perforce.Resolve"; static const char * const CMD_ID_SUBMIT = "Perforce.Submit"; @@ -156,21 +159,31 @@ static const char * const CMD_ID_SEPARATOR3 = "Perforce.Separator3"; // PerforcePlugin //// +namespace Perforce { +namespace Internal { + +PerforceResponse::PerforceResponse() : + error(true), + exitCode(-1) +{ +} + PerforcePlugin *PerforcePlugin::m_perforcePluginInstance = NULL; PerforcePlugin::PerforcePlugin() : - VCSBase::VCSBasePlugin(QLatin1String(Constants::PERFORCESUBMITEDITOR_KIND)), + VCSBase::VCSBasePlugin(QLatin1String(Constants::PERFORCE_SUBMIT_EDITOR_KIND)), m_editAction(0), m_addAction(0), m_deleteAction(0), m_openedAction(0), - m_revertAction(0), - m_diffCurrentAction(0), + m_revertFileAction(0), + m_diffFileAction(0), m_diffProjectAction(0), m_updateProjectAction(0), + m_revertProjectAction(0), + m_revertUnchangedAction(0), m_diffAllAction(0), - m_resolveAction(0), - m_submitAction(0), + m_submitProjectAction(0), m_pendingAction(0), m_describeAction(0), m_annotateCurrentAction(0), @@ -188,8 +201,8 @@ PerforcePlugin::PerforcePlugin() : static const VCSBase::VCSBaseSubmitEditorParameters submitParameters = { Perforce::Constants::SUBMIT_MIMETYPE, - Perforce::Constants::PERFORCESUBMITEDITOR_KIND, - Perforce::Constants::C_PERFORCESUBMITEDITOR + Perforce::Constants::PERFORCE_SUBMIT_EDITOR_KIND, + Perforce::Constants::PERFORCESUBMITEDITOR_CONTEXT }; bool PerforcePlugin::initialize(const QStringList & /* arguments */, QString * errorMessage) @@ -234,7 +247,7 @@ bool PerforcePlugin::initialize(const QStringList & /* arguments */, QString * e QList<int> perforcesubmitcontext; perforcesubmitcontext << Core::UniqueIDManager::instance()-> - uniqueIdentifier(Constants::C_PERFORCESUBMITEDITOR); + uniqueIdentifier(Constants::PERFORCESUBMITEDITOR_CONTEXT); Core::Command *command; QAction *tmpaction; @@ -262,12 +275,12 @@ bool PerforcePlugin::initialize(const QStringList & /* arguments */, QString * e connect(m_deleteAction, SIGNAL(triggered()), this, SLOT(deleteCurrentFile())); mperforce->addAction(command); - m_revertAction = new Utils::ParameterAction(tr("Revert"), tr("Revert \"%1\""), Utils::ParameterAction::EnabledWithParameter, this); - command = am->registerAction(m_revertAction, CMD_ID_REVERT, globalcontext); + m_revertFileAction = new Utils::ParameterAction(tr("Revert"), tr("Revert \"%1\""), Utils::ParameterAction::EnabledWithParameter, this); + command = am->registerAction(m_revertFileAction, CMD_ID_REVERT, globalcontext); command->setAttribute(Core::Command::CA_UpdateText); command->setDefaultKeySequence(QKeySequence(tr("Alt+P,Alt+R"))); command->setDefaultText(tr("Revert File")); - connect(m_revertAction, SIGNAL(triggered()), this, SLOT(revertCurrentFile())); + connect(m_revertFileAction, SIGNAL(triggered()), this, SLOT(revertCurrentFile())); mperforce->addAction(command); tmpaction = new QAction(this); @@ -275,11 +288,11 @@ bool PerforcePlugin::initialize(const QStringList & /* arguments */, QString * e command = am->registerAction(tmpaction, QLatin1String("Perforce.Sep.Edit"), globalcontext); mperforce->addAction(command); - m_diffCurrentAction = new Utils::ParameterAction(tr("Diff Current File"), tr("Diff \"%1\""), Utils::ParameterAction::EnabledWithParameter, this); - command = am->registerAction(m_diffCurrentAction, CMD_ID_DIFF_CURRENT, globalcontext); + m_diffFileAction = new Utils::ParameterAction(tr("Diff Current File"), tr("Diff \"%1\""), Utils::ParameterAction::EnabledWithParameter, this); + command = am->registerAction(m_diffFileAction, CMD_ID_DIFF_CURRENT, globalcontext); command->setAttribute(Core::Command::CA_UpdateText); command->setDefaultText(tr("Diff Current File")); - connect(m_diffCurrentAction, SIGNAL(triggered()), this, SLOT(diffCurrentFile())); + connect(m_diffFileAction, SIGNAL(triggered()), this, SLOT(diffCurrentFile())); mperforce->addAction(command); const QString diffProjectDefaultText = tr("Diff Current Project/Session"); @@ -307,10 +320,11 @@ bool PerforcePlugin::initialize(const QStringList & /* arguments */, QString * e connect(m_openedAction, SIGNAL(triggered()), this, SLOT(printOpenedFileList())); mperforce->addAction(command); - m_submitAction = new QAction(tr("Submit Project"), this); - command = am->registerAction(m_submitAction, CMD_ID_SUBMIT, globalcontext); + m_submitProjectAction = new Utils::ParameterAction(tr("Submit Project"), tr("Submit Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this); + command = am->registerAction(m_submitProjectAction, CMD_ID_SUBMIT, globalcontext); + command->setAttribute(Core::Command::CA_UpdateText); command->setDefaultKeySequence(QKeySequence(tr("Alt+P,Alt+S"))); - connect(m_submitAction, SIGNAL(triggered()), this, SLOT(submit())); + connect(m_submitProjectAction, SIGNAL(triggered()), this, SLOT(startSubmitProject())); mperforce->addAction(command); m_pendingAction = new QAction(tr("Pending Changes..."), this); @@ -318,7 +332,7 @@ bool PerforcePlugin::initialize(const QStringList & /* arguments */, QString * e connect(m_pendingAction, SIGNAL(triggered()), this, SLOT(printPendingChanges())); mperforce->addAction(command); - const QString updateProjectDefaultText = tr("Update Current Project/Session"); + const QString updateProjectDefaultText = tr("Update Current Project"); m_updateProjectAction = new Utils::ParameterAction(updateProjectDefaultText, tr("Update Project \"%1\""), Utils::ParameterAction::AlwaysEnabled, this); command = am->registerAction(m_updateProjectAction, CMD_ID_UPDATE_PROJECT, globalcontext); command->setDefaultText(updateProjectDefaultText); @@ -326,6 +340,18 @@ bool PerforcePlugin::initialize(const QStringList & /* arguments */, QString * e connect(m_updateProjectAction, SIGNAL(triggered()), this, SLOT(updateCurrentProject())); mperforce->addAction(command); + m_revertProjectAction = new Utils::ParameterAction(tr("Revert Project"), tr("Revert Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this); + command = am->registerAction(m_revertProjectAction, CMD_ID_REVERT_PROJECT, globalcontext); + command->setAttribute(Core::Command::CA_UpdateText); + connect(m_revertProjectAction, SIGNAL(triggered()), this, SLOT(revertCurrentProject())); + mperforce->addAction(command); + + m_revertUnchangedAction = new Utils::ParameterAction(tr("Revert Unchanged"), tr("Revert Unchanged Files of Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this); + command = am->registerAction(m_revertUnchangedAction, CMD_ID_REVERT_UNCHANGED_PROJECT, globalcontext); + command->setAttribute(Core::Command::CA_UpdateText); + connect(m_revertUnchangedAction, SIGNAL(triggered()), this, SLOT(revertUnchangedCurrentProject())); + mperforce->addAction(command); + tmpaction = new QAction(this); tmpaction->setSeparator(true); command = am->registerAction(tmpaction, QLatin1String("Perforce.Sep.Changes"), globalcontext); @@ -384,138 +410,160 @@ bool PerforcePlugin::initialize(const QStringList & /* arguments */, QString * e void PerforcePlugin::extensionsInitialized() { - m_projectExplorer = ProjectExplorer::ProjectExplorerPlugin::instance(); + getTopLevel(); } void PerforcePlugin::openCurrentFile() { - vcsOpen(currentFileName()); + const VCSBase::VCSBasePluginState state = currentState(); + QTC_ASSERT(state.hasFile(), return) + vcsOpen(state.currentFileTopLevel(), state.relativeCurrentFile()); } void PerforcePlugin::addCurrentFile() { - vcsAdd(currentFileName()); + const VCSBase::VCSBasePluginState state = currentState(); + QTC_ASSERT(state.hasFile(), return) + vcsAdd(state.currentFileTopLevel(), state.relativeCurrentFile()); } void PerforcePlugin::deleteCurrentFile() { - vcsDelete(currentFileName()); + const VCSBase::VCSBasePluginState state = currentState(); + QTC_ASSERT(state.hasFile(), return) + vcsDelete(state.currentFileTopLevel(), state.relativeCurrentFile()); } void PerforcePlugin::revertCurrentFile() { - const QString fileName = currentFileName(); - QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(fileName); + const VCSBase::VCSBasePluginState state = currentState(); + QTC_ASSERT(state.hasFile(), return) + + QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(state.currentFile()); QStringList args; - args << QLatin1String("diff") << QLatin1String("-sa"); - PerforceResponse result = runP4Cmd(args, QStringList(), CommandToWindow|StdErrToWindow|ErrorToWindow, codec); + args << QLatin1String("diff") << QLatin1String("-sa") << state.relativeCurrentFile(); + PerforceResponse result = runP4Cmd(state.currentFileTopLevel(), args, + RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow, + QStringList(), QByteArray(), codec); if (result.error) return; + // "foo.cpp - file(s) not opened on this client." + if (result.stdOut.isEmpty() || result.stdOut.contains(QLatin1String(" - "))) + return; - if (!result.stdOut.isEmpty()) { - bool doNotRevert = QMessageBox::warning(0, tr("p4 revert"), - tr("The file has been changed. Do you want to revert it?"), - QMessageBox::Yes, QMessageBox::No) - == QMessageBox::No; - if (doNotRevert) - return; - } + const bool doNotRevert = QMessageBox::warning(0, tr("p4 revert"), + tr("The file has been changed. Do you want to revert it?"), + QMessageBox::Yes, QMessageBox::No) == QMessageBox::No; + if (doNotRevert) + return; - Core::FileChangeBlocker fcb(fileName); + Core::FileChangeBlocker fcb(state.currentFile()); fcb.setModifiedReload(true); - PerforceResponse result2 = runP4Cmd(QStringList() << QLatin1String("revert") << fileName, QStringList(), CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow); + args.clear(); + args << QLatin1String("revert") << state.relativeCurrentFile(); + PerforceResponse result2 = runP4Cmd(state.currentFileTopLevel(), args, + CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow); if (!result2.error) - perforceVersionControl()->emitFilesChanged(QStringList(fileName)); + perforceVersionControl()->emitFilesChanged(QStringList(state.currentFile())); } void PerforcePlugin::diffCurrentFile() { - p4Diff(QStringList(currentFileName())); + const VCSBase::VCSBasePluginState state = currentState(); + QTC_ASSERT(state.hasFile(), return) + p4Diff(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile())); } void PerforcePlugin::diffCurrentProject() { - QString name; - const QStringList nativeFiles = currentProjectFiles(&name); - p4Diff(nativeFiles, name); + const VCSBase::VCSBasePluginState state = currentState(); + QTC_ASSERT(state.hasProject(), return) + p4Diff(state.currentProjectTopLevel(), perforceRelativeProjectDirectory(state)); } void PerforcePlugin::diffAllOpened() { - p4Diff(QStringList()); + p4Diff(m_settings.topLevel(), QStringList()); } -// Add the directory of a project to a list of p4 directory specifications -// ("path/...") -static inline void addProjectP4Directory(const ProjectExplorer::Project *p, QStringList *p4dirs) +void PerforcePlugin::updateCurrentProject() { - if (const Core::IFile *file = p->file()) { - const QFileInfo fi(file->fileName()); - QString p4dir = fi.absolutePath(); - if (!p4dir.isEmpty()) { - p4dir += QDir::separator(); - p4dir += QLatin1String("..."); - p4dirs->push_back(p4dir); - } - } + const VCSBase::VCSBasePluginState state = currentState(); + QTC_ASSERT(state.hasProject(), return) + updateCheckout(state.currentProjectTopLevel(), perforceRelativeProjectDirectory(state)); } -void PerforcePlugin::updateCurrentProject() +void PerforcePlugin::updateAll() +{ + updateCheckout(m_settings.topLevel()); +} + +void PerforcePlugin::revertCurrentProject() { - if (!m_projectExplorer) + const VCSBase::VCSBasePluginState state = currentState(); + QTC_ASSERT(state.hasProject(), return) + + const QString msg = tr("Do you want to revert all changes to the project \"%1\"?").arg(state.currentProjectName()); + if (QMessageBox::warning(0, tr("p4 revert"), msg, QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) return; - // Compile a list of project directories - QStringList p4Directories; - if (const ProjectExplorer::Project *proj = m_projectExplorer->currentProject()) { - addProjectP4Directory(proj, &p4Directories); - } else { - if (const ProjectExplorer::SessionManager *session = m_projectExplorer->session()) - foreach(const ProjectExplorer::Project *proj, session->projects()) - addProjectP4Directory(proj, &p4Directories); - } - if (!p4Directories.empty()) - updateCheckout(p4Directories); + revertProject(state.currentProjectTopLevel(), perforceRelativeProjectDirectory(state), false); } -void PerforcePlugin::updateAll() +void PerforcePlugin::revertUnchangedCurrentProject() { - updateCheckout(); + // revert -a. + const VCSBase::VCSBasePluginState state = currentState(); + QTC_ASSERT(state.hasProject(), return) + revertProject(state.currentProjectTopLevel(), perforceRelativeProjectDirectory(state), true); } -void PerforcePlugin::updateCheckout(const QStringList &dirs) +bool PerforcePlugin::revertProject(const QString &workingDir, const QStringList &pathArgs, bool unchangedOnly) +{ + QStringList args(QLatin1String("revert")); + if (unchangedOnly) + args.push_back(QLatin1String("-a")); + args.append(pathArgs); + const PerforceResponse resp = runP4Cmd(workingDir, args, + RunFullySynchronous|CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow); + return !resp.error; +} + +void PerforcePlugin::updateCheckout(const QString &workingDir, const QStringList &dirs) { QStringList args(QLatin1String("sync")); args.append(dirs); - const PerforceResponse resp = runP4Cmd(args, QStringList(), CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow); - if (!dirs.empty()) + const PerforceResponse resp = runP4Cmd(workingDir, args, + CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow); + if (dirs.empty()) { + if (!workingDir.isEmpty()) + perforceVersionControl()->emitRepositoryChanged(workingDir); + } else { + const QChar slash = QLatin1Char('/'); foreach(const QString &dir, dirs) - perforceVersionControl()->emitRepositoryChanged(dir); + perforceVersionControl()->emitRepositoryChanged(workingDir + slash + dir); + } } void PerforcePlugin::printOpenedFileList() { - Core::IEditor *e = Core::EditorManager::instance()->currentEditor(); - if (e) - e->widget()->setFocus(); - PerforceResponse result = runP4Cmd(QStringList() << QLatin1String("opened"), QStringList(), CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow); + runP4Cmd(m_settings.topLevel(), QStringList(QLatin1String("opened")), + CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow); } -void PerforcePlugin::submit() +void PerforcePlugin::startSubmitProject() { - if (VCSBase::VCSBaseSubmitEditor::raiseSubmitEditor()) - return; - QString errorMessage; - if (!checkP4Configuration(&errorMessage)) { - VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage); + if (VCSBase::VCSBaseSubmitEditor::raiseSubmitEditor()) return; - } if (isCommitEditorOpen()) { VCSBase::VCSBaseOutputWindow::instance()->appendWarning(tr("Another submit is currently executed.")); return; } + const VCSBase::VCSBasePluginState state = currentState(); + QTC_ASSERT(state.hasProject(), return) + QTemporaryFile changeTmpFile; changeTmpFile.setAutoRemove(false); if (!changeTmpFile.open()) { @@ -523,9 +571,15 @@ void PerforcePlugin::submit() cleanCommitMessageFile(); return; } + // Revert all unchanged files. + if (!revertProject(state.currentProjectTopLevel(), perforceRelativeProjectDirectory(state), true)) + return; + // Start a change + QStringList args; - PerforceResponse result = runP4Cmd(QStringList()<< QLatin1String("change") << QLatin1String("-o"), QStringList(), - CommandToWindow|StdErrToWindow|ErrorToWindow); + args << QLatin1String("change") << QLatin1String("-o"); + PerforceResponse result = runP4Cmd(state.currentProjectTopLevel(), args, + RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow); if (result.error) { cleanCommitMessageFile(); return; @@ -535,19 +589,19 @@ void PerforcePlugin::submit() changeTmpFile.write(result.stdOut.toAscii()); changeTmpFile.close(); - // Assemble file list of project - QString name; - const QStringList nativeFiles = currentProjectFiles(&name); - PerforceResponse result2 = runP4Cmd(QStringList(QLatin1String("fstat")), nativeFiles, - CommandToWindow|StdErrToWindow|ErrorToWindow); - if (result2.error) { + args.clear(); + args << QLatin1String("fstat"); + args.append(perforceRelativeProjectDirectory(state)); + PerforceResponse fstatResult = runP4Cmd(state.currentProjectTopLevel(), args, + RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow); + if (fstatResult.error) { cleanCommitMessageFile(); return; } - QStringList stdOutLines = result2.stdOut.split(QLatin1Char('\n')); + QStringList fstatLines = fstatResult.stdOut.split(QLatin1Char('\n')); QStringList depotFileNames; - foreach (const QString &line, stdOutLines) { + foreach (const QString &line, fstatLines) { if (line.startsWith("... depotFile")) depotFileNames.append(line.mid(14)); } @@ -563,12 +617,12 @@ void PerforcePlugin::submit() Core::IEditor *PerforcePlugin::openPerforceSubmitEditor(const QString &fileName, const QStringList &depotFileNames) { Core::EditorManager *editorManager = Core::EditorManager::instance(); - Core::IEditor *editor = editorManager->openEditor(fileName, Constants::PERFORCESUBMITEDITOR_KIND); + Core::IEditor *editor = editorManager->openEditor(fileName, Constants::PERFORCE_SUBMIT_EDITOR_KIND); editorManager->ensureEditorManagerVisible(); PerforceSubmitEditor *submitEditor = static_cast<PerforceSubmitEditor*>(editor); submitEditor->restrictToProjectFiles(depotFileNames); submitEditor->registerActions(m_undoAction, m_redoAction, m_submitCurrentLogAction, m_diffSelectedFiles); - connect(submitEditor, SIGNAL(diffSelectedFiles(QStringList)), this, SLOT(slotDiff(QStringList))); + connect(submitEditor, SIGNAL(diffSelectedFiles(QStringList)), this, SLOT(slotSubmitDiff(QStringList))); return editor; } @@ -581,7 +635,8 @@ void PerforcePlugin::printPendingChanges() const int i = dia.changeNumber(); QStringList args(QLatin1String("submit")); args << QLatin1String("-c") << QString::number(i); - runP4Cmd(args, QStringList(), CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow); + runP4Cmd(m_settings.topLevel(), args, + CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow); } } @@ -594,29 +649,35 @@ void PerforcePlugin::describeChange() void PerforcePlugin::annotateCurrentFile() { - const QString file = currentFileName(); - if (!file.isEmpty()) - annotate(file); + const VCSBase::VCSBasePluginState state = currentState(); + QTC_ASSERT(state.hasFile(), return) + annotate(state.currentFileTopLevel(), state.relativeCurrentFile()); } void PerforcePlugin::annotate() { - const QString file = QFileDialog::getOpenFileName(0, tr("p4 annotate"), currentFileName()); - if (!file.isEmpty()) - annotate(file); + const QString file = QFileDialog::getOpenFileName(0, tr("p4 annotate")); + if (!file.isEmpty()) { + const QFileInfo fi(file); + annotate(fi.absolutePath(), fi.fileName()); + } } -void PerforcePlugin::annotate(const QString &fileName) +void PerforcePlugin::annotate(const QString &workingDir, const QString &fileName) { - QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(fileName); + const QStringList files = QStringList(fileName); + QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(workingDir, files); + const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, files); + const QString source = VCSBase::VCSBaseEditor::getSource(workingDir, files); QStringList args; args << QLatin1String("annotate") << QLatin1String("-cqi") << fileName; - const PerforceResponse result = runP4Cmd(args, QStringList(), - CommandToWindow|StdErrToWindow|ErrorToWindow, codec); + const PerforceResponse result = runP4Cmd(workingDir, args, + CommandToWindow|StdErrToWindow|ErrorToWindow, + QStringList(), QByteArray(), codec); if (!result.error) { - const int lineNumber = VCSBase::VCSBaseEditor::lineNumberOfCurrentEditor(fileName); + const int lineNumber = VCSBase::VCSBaseEditor::lineNumberOfCurrentEditor(); const QFileInfo fi(fileName); - Core::IEditor *ed = showOutputInEditor(tr("p4 annotate %1").arg(fi.fileName()), + Core::IEditor *ed = showOutputInEditor(tr("p4 annotate %1").arg(id), result.stdOut, VCSBase::AnnotateOutput, codec); VCSBase::VCSBaseEditor::gotoLineOfEditor(ed, lineNumber); } @@ -624,30 +685,32 @@ void PerforcePlugin::annotate(const QString &fileName) void PerforcePlugin::filelogCurrentFile() { - const QString file = currentFileName(); - if (!file.isEmpty()) - filelog(file); + const VCSBase::VCSBasePluginState state = currentState(); + QTC_ASSERT(state.hasFile(), return) + filelog(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile())); } void PerforcePlugin::filelog() { - const QString file = QFileDialog::getOpenFileName(0, tr("p4 filelog"), currentFileName()); - if (!file.isEmpty()) - filelog(file); + const QString file = QFileDialog::getOpenFileName(0, tr("p4 filelog")); + if (!file.isEmpty()) { + const QFileInfo fi(file); + filelog(fi.absolutePath(), QStringList(fi.fileName())); + } } -void PerforcePlugin::filelog(const QString &fileName) +void PerforcePlugin::filelog(const QString &workingDir, const QStringList &fileNames) { - QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(fileName); + const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, fileNames); + QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(workingDir, fileNames); QStringList args; - args << QLatin1String("filelog") << QLatin1String("-li") << fileName; - const PerforceResponse result = runP4Cmd(args, QStringList(), - CommandToWindow|StdErrToWindow|ErrorToWindow, codec); - if (!result.error) { - const QFileInfo fi(fileName); - showOutputInEditor(tr("p4 filelog %1").arg(fi.fileName()), - result.stdOut, VCSBase::LogOutput, codec); - } + args << QLatin1String("filelog") << QLatin1String("-li"); + args.append(fileNames); + const PerforceResponse result = runP4Cmd(workingDir, args, + CommandToWindow|StdErrToWindow|ErrorToWindow, + QStringList(), QByteArray(), codec); + if (!result.error) + showOutputInEditor(tr("p4 filelog %1").arg(id), result.stdOut, VCSBase::LogOutput, codec); } void PerforcePlugin::updateActions(VCSBase::VCSBasePlugin::ActionState as) @@ -655,33 +718,21 @@ void PerforcePlugin::updateActions(VCSBase::VCSBasePlugin::ActionState as) if (!VCSBase::VCSBasePlugin::enableMenuAction(as, m_menuAction)) return; - const QString fileName = currentFileName(); - const QString baseName = fileName.isEmpty() ? fileName : QFileInfo(fileName).fileName(); - - m_editAction->setParameter(baseName); - m_addAction->setParameter(baseName); - m_deleteAction->setParameter(baseName); - m_revertAction->setParameter(baseName); - m_diffCurrentAction->setParameter(baseName); - m_annotateCurrentAction->setParameter(baseName); - m_filelogCurrentAction->setParameter(baseName); - - // The project actions refer to the current project or the projects - // of a session, provided there are any. - if (m_projectExplorer && m_projectExplorer->currentProject()) { - const QString currentProjectName = m_projectExplorer->currentProject()->name(); - m_updateProjectAction->setParameter(currentProjectName); - m_diffProjectAction->setParameter(currentProjectName); - m_updateProjectAction->setEnabled(true); - m_diffProjectAction->setEnabled(true); - } else { - // Nope, either all projects of a session or none - m_updateProjectAction->setParameter(QString()); - m_diffProjectAction->setParameter(QString()); - const bool enabled = m_projectExplorer && m_projectExplorer->session() && !m_projectExplorer->session()->projects().empty(); - m_updateProjectAction->setEnabled(enabled); - m_diffProjectAction->setEnabled(enabled); - } + const QString fileName = currentState().currentFileName(); + m_editAction->setParameter(fileName); + m_addAction->setParameter(fileName); + m_deleteAction->setParameter(fileName); + m_revertFileAction->setParameter(fileName); + m_diffFileAction->setParameter(fileName); + m_annotateCurrentAction->setParameter(fileName); + m_filelogCurrentAction->setParameter(fileName); + + const QString projectName = currentState().currentProjectName(); + m_updateProjectAction->setParameter(projectName); + m_diffProjectAction->setParameter(projectName); + m_submitProjectAction->setParameter(projectName); + m_revertProjectAction->setParameter(projectName); + m_revertUnchangedAction->setParameter(projectName); m_diffAllAction->setEnabled(true); m_openedAction->setEnabled(true); @@ -692,56 +743,73 @@ void PerforcePlugin::updateActions(VCSBase::VCSBasePlugin::ActionState as) m_updateAllAction->setEnabled(true); } -bool PerforcePlugin::managesDirectory(const QString &directory) const +bool PerforcePlugin::managesDirectory(const QString &directory) { - if (!checkP4Configuration()) + if (!m_settings.isValid()) return false; - const QString p4Path = directory + QLatin1String("/..."); - QStringList args; - args << QLatin1String("fstat") << QLatin1String("-m1") << p4Path; - - const PerforceResponse result = runP4Cmd(args, QStringList(), 0u); - return result.stdOut.contains("depotFile") || result.stdErr.contains("... - no such file(s)"); + // Cached? + const ManagedDirectoryCache::const_iterator cit = m_managedDirectoryCache.constFind(directory); + if (cit != m_managedDirectoryCache.constEnd()) + return cit.value(); + // Determine value and insert into cache + bool managed = false; + do { + // Quick check: Must be at or below top level and not "../../other_path" + const QStringList relativeDirArgs = m_settings.relativeToTopLevelArguments(directory); + if (!relativeDirArgs.empty() && relativeDirArgs.front().startsWith(QLatin1String(".."))) + break; + // Is it actually managed by perforce? + QStringList args; + args << QLatin1String("fstat") << QLatin1String("-m1") << perforceRelativeFileArguments(relativeDirArgs); + const PerforceResponse result = runP4Cmd(m_settings.topLevel(), args, + RunFullySynchronous); + managed = result.stdOut.contains("depotFile") || result.stdErr.contains("... - no such file(s)"); + } while(false); + + m_managedDirectoryCache.insert(directory, managed); + return managed; } -QString PerforcePlugin::findTopLevelForDirectory(const QString & /* dir */) const +QString PerforcePlugin::findTopLevelForDirectory(const QString &dir) { - // First check with p4 client -o - PerforceResponse result = runP4Cmd(QStringList() << QLatin1String("client") << QLatin1String("-o"), QStringList(), 0u); - if (result.error) - return QString::null; - - QRegExp regExp(QLatin1String("(\\n|\\r\\n|\\r)Root:\\s*(.*)(\\n|\\r\\n|\\r)")); - QTC_ASSERT(regExp.isValid(), /**/); - regExp.setMinimal(true); - if (regExp.indexIn(result.stdOut) != -1) { - QString file = regExp.cap(2).trimmed(); - if (QFileInfo(file).exists()) - return file; - } - return QString::null; + if (!m_settings.isValid()) + return QString(); + return managesDirectory(dir) ? m_settings.topLevelSymLinkTarget() : QString(); } -bool PerforcePlugin::vcsOpen(const QString &fileName) +bool PerforcePlugin::vcsOpen(const QString &workingDir, const QString &fileName) { - PerforceResponse result = runP4Cmd(QStringList() << QLatin1String("edit") << QDir::toNativeSeparators(fileName), QStringList(), + if (Perforce::Constants::debug) + qDebug() << "PerforcePlugin::vcsOpen" << workingDir << fileName; + QStringList args; + args << QLatin1String("edit") << QDir::toNativeSeparators(fileName); + const PerforceResponse result = runP4Cmd(workingDir, args, CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow); return !result.error; } -bool PerforcePlugin::vcsAdd(const QString &fileName) +bool PerforcePlugin::vcsAdd(const QString &workingDir, const QString &fileName) { - PerforceResponse result = runP4Cmd(QStringList() << QLatin1String("add") << fileName, QStringList(), + QStringList args; + args << QLatin1String("add") << fileName; + const PerforceResponse result = runP4Cmd(workingDir, args, CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow); return !result.error; } -bool PerforcePlugin::vcsDelete(const QString &fileName) +bool PerforcePlugin::vcsDelete(const QString &workingDir, const QString &fileName) { - PerforceResponse result = runP4Cmd(QStringList() << QLatin1String("revert") << fileName, QStringList(), + + QStringList args; + args << QLatin1String("revert") << fileName; + const PerforceResponse revertResult = runP4Cmd(workingDir, args, CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow); - PerforceResponse result2 = runP4Cmd(QStringList() << QLatin1String("delete") << fileName, QStringList(), - CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow); + if (revertResult.error) + return false; + args.clear(); + args << QLatin1String("delete") << fileName; + const PerforceResponse deleteResult = runP4Cmd(workingDir, args, + CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow); // TODO need to carefully parse the actual messages from perforce // or do a fstat before to decide what to do @@ -749,7 +817,7 @@ bool PerforcePlugin::vcsDelete(const QString &fileName) // File is in depot and unopened => p4 delete % // File is in depot and opened => p4 revert %, p4 delete % // File is not in depot => p4 revert % - return !(result.error && result2.error); + return !deleteResult.error; } static QString formatCommand(const QString &cmd, const QStringList &args) @@ -761,66 +829,97 @@ static QString formatCommand(const QString &cmd, const QStringList &args) return PerforcePlugin::tr("Executing: %1\n").arg(command); } -PerforceResponse PerforcePlugin::runP4Cmd(const QStringList &args, - const QStringList &extraArgs, - unsigned logFlags, - QTextCodec *outputCodec) const +// Write extra args to temporary file +QSharedPointer<QTemporaryFile> + PerforcePlugin::createTemporaryArgumentFile(const QStringList &extraArgs) const { - if (Perforce::Constants::debug) - qDebug() << "PerforcePlugin::runP4Cmd" << args << extraArgs << debugCodec(outputCodec); - PerforceResponse response; - response.error = true; - VCSBase::VCSBaseOutputWindow *outputWindow = VCSBase::VCSBaseOutputWindow::instance(); - if (!checkP4Configuration(&response.message)) { - outputWindow->appendError(response.message); - return response; + if (extraArgs.isEmpty()) + return QSharedPointer<QTemporaryFile>(); + // create pattern + if (m_tempFilePattern.isEmpty()) { + m_tempFilePattern = QDir::tempPath(); + if (!m_tempFilePattern.endsWith(QDir::separator())) + m_tempFilePattern += QDir::separator(); + m_tempFilePattern += QLatin1String("qtc_p4_XXXXXX.args"); } - - // handle extra args - QTemporaryFile tempfile; - tempfile.setAutoRemove(true); - const QChar newLine = QLatin1Char('\n'); - const QChar blank = QLatin1Char(' '); - QStringList actualArgs = m_settings.basicP4Args(); - if (!extraArgs.isEmpty()) { - if (tempfile.open()) { - QTextStream stream(&tempfile); - stream << extraArgs.join(QString(newLine)); - actualArgs << QLatin1String("-x") << tempfile.fileName(); - tempfile.close(); - } else { - qWarning()<<"Could not create temporary file. Appending all file names to command line."; - actualArgs << extraArgs; - } + QSharedPointer<QTemporaryFile> rc(new QTemporaryFile(m_tempFilePattern)); + rc->setAutoRemove(true); + if (!rc->open()) { + qWarning("Could not create temporary file: %s. Appending all file names to command line.", + qPrintable(rc->errorString())); + return QSharedPointer<QTemporaryFile>(); } - actualArgs << args; + const int last = extraArgs.size() - 1; + for (int i = 0; i <= last; i++) { + rc->write(extraArgs.at(i).toLocal8Bit()); + if (i != last) + rc->write("\n"); + } + rc->close(); + return rc; +} - if (logFlags & CommandToWindow) - outputWindow->appendCommand(formatCommand(m_settings.p4Command(), actualArgs)); +// Run messages + +static inline QString msgNotStarted(const QString &cmd) +{ + return PerforcePlugin::tr("Could not start perforce '%1'. Please check your settings in the preferences.").arg(cmd); +} + +static inline QString msgTimeout() +{ + return PerforcePlugin::tr("Perforce did not respond within timeout limit (%1 ms).").arg(p4Timeout ); +} + +static inline QString msgCrash() +{ + return PerforcePlugin::tr("The process terminated abnormally."); +} + +static inline QString msgExitCode(int ex) +{ + return PerforcePlugin::tr("The process terminated with exit code %1.").arg(ex); +} + +// Run using a SynchronousProcess, emitting signals to the message window +PerforceResponse PerforcePlugin::synchronousProcess(const QString &workingDir, + const QStringList &args, + unsigned flags, + const QByteArray &stdInput, + QTextCodec *outputCodec) const +{ + QTC_ASSERT(stdInput.isEmpty(), return PerforceResponse()) // Not supported here + VCSBase::VCSBaseOutputWindow *outputWindow = VCSBase::VCSBaseOutputWindow::instance(); // Run, connect stderr to the output window Utils::SynchronousProcess process; process.setTimeout(p4Timeout); process.setStdOutCodec(outputCodec); - process.setEnvironment(environment()); + if (flags & OverrideDiffEnvironment) + process.setProcessEnvironment(overrideDiffEnvironmentVariable()); + if (!workingDir.isEmpty()) + process.setWorkingDirectory(workingDir); // connect stderr to the output window if desired - if (logFlags & StdErrToWindow) { + if (flags & StdErrToWindow) { process.setStdErrBufferedSignalsEnabled(true); connect(&process, SIGNAL(stdErrBuffered(QString,bool)), outputWindow, SLOT(append(QString))); } // connect stdout to the output window if desired - if (logFlags & StdOutToWindow) { + if (flags & StdOutToWindow) { process.setStdOutBufferedSignalsEnabled(true); connect(&process, SIGNAL(stdOutBuffered(QString,bool)), outputWindow, SLOT(append(QString))); } - - const Utils::SynchronousProcessResponse sp_resp = process.run(m_settings.p4Command(), actualArgs); + if (Perforce::Constants::debug) + qDebug() << "PerforcePlugin::run syncp actual args [" << process.workingDirectory() << ']' << args; + const Utils::SynchronousProcessResponse sp_resp = process.run(m_settings.p4Command(), args); if (Perforce::Constants::debug) qDebug() << sp_resp; + PerforceResponse response; response.error = true; + response.exitCode = sp_resp.exitCode; response.stdErr = sp_resp.stdErr; response.stdOut = sp_resp.stdOut; switch (sp_resp.result) { @@ -828,22 +927,125 @@ PerforceResponse PerforcePlugin::runP4Cmd(const QStringList &args, response.error = false; break; case Utils::SynchronousProcessResponse::FinishedError: - response.message = tr("The process terminated with exit code %1.").arg(sp_resp.exitCode); + response.message = msgExitCode(sp_resp.exitCode); + response.error = !(flags & IgnoreExitCode); break; case Utils::SynchronousProcessResponse::TerminatedAbnormally: - response.message = tr("The process terminated abnormally."); + response.message = msgCrash(); break; case Utils::SynchronousProcessResponse::StartFailed: - response.message = tr("Could not start perforce '%1'. Please check your settings in the preferences.").arg(m_settings.p4Command()); + response.message = msgNotStarted(m_settings.p4Command()); break; case Utils::SynchronousProcessResponse::Hang: - response.message = tr("Perforce did not respond within timeout limit (%1 ms).").arg(p4Timeout ); + response.message = msgCrash(); break; } + return response; +} + +// Run using a QProcess, for short queries +PerforceResponse PerforcePlugin::fullySynchronousProcess(const QString &workingDir, + const QStringList &args, + unsigned flags, + const QByteArray &stdInput, + QTextCodec *outputCodec) const +{ + QProcess process; + + if (flags & OverrideDiffEnvironment) + process.setProcessEnvironment(overrideDiffEnvironmentVariable()); + if (!workingDir.isEmpty()) + process.setWorkingDirectory(workingDir); + + if (Perforce::Constants::debug) + qDebug() << "PerforcePlugin::run fully syncp actual args [" << process.workingDirectory() << ']' << args; + + PerforceResponse response; + process.start(m_settings.p4Command(), args); + if (stdInput.isEmpty()) + process.closeWriteChannel(); + + if (!process.waitForStarted(3000)) { + response.error = true; + response.message = msgNotStarted(m_settings.p4Command()); + return response; + } + if (!stdInput.isEmpty()) { + if (process.write(stdInput) == -1) { + response.error = true; + response.message = tr("Unable to write input data to process %1: %2").arg(m_settings.p4Command(), process.errorString()); + return response; + } + process.closeWriteChannel(); + } + + if (!process.waitForFinished(p4Timeout)) { + PerforceChecker::ensureProcessStopped(process); + response.error = true; + response.message = msgTimeout(); + return response; + } + if (process.exitStatus() != QProcess::NormalExit) { + response.error = true; + response.message = msgCrash(); + return response; + } + response.exitCode = process.exitCode(); + response.error = response.exitCode ? !(flags & IgnoreExitCode) : false; + response.stdErr = QString::fromLocal8Bit(process.readAllStandardError()); + const QByteArray stdOut = process.readAllStandardOutput(); + response.stdOut = outputCodec ? outputCodec->toUnicode(stdOut.constData(), stdOut.size()) : + QString::fromLocal8Bit(stdOut); + // Logging + VCSBase::VCSBaseOutputWindow *outputWindow = VCSBase::VCSBaseOutputWindow::instance(); + if ((flags & StdErrToWindow) && !response.stdErr.isEmpty()) + outputWindow->append(response.stdErr); + if ((flags & StdOutToWindow) && !response.stdOut.isEmpty()) + outputWindow->append(response.stdOut); + return response; +} + +PerforceResponse PerforcePlugin::runP4Cmd(const QString &workingDir, + const QStringList &args, + unsigned flags, + const QStringList &extraArgs, + const QByteArray &stdInput, + QTextCodec *outputCodec) const +{ + if (Perforce::Constants::debug) + qDebug() << "PerforcePlugin::runP4Cmd [" << workingDir << ']' << args << extraArgs << stdInput << debugCodec(outputCodec); + + VCSBase::VCSBaseOutputWindow *outputWindow = VCSBase::VCSBaseOutputWindow::instance(); + if (!m_settings.isValid()) { + PerforceResponse invalidConfigResponse; + invalidConfigResponse.error = true; + invalidConfigResponse.message = tr("Perforce is not correctly configured."); + outputWindow->appendError(invalidConfigResponse.message); + return invalidConfigResponse; + } + QStringList actualArgs = m_settings.commonP4Arguments(workingDir); + QSharedPointer<QTemporaryFile> tempFile = createTemporaryArgumentFile(extraArgs); + if (!tempFile.isNull()) + actualArgs << QLatin1String("-x") << tempFile->fileName(); + actualArgs.append(args); + + if (flags & CommandToWindow) + outputWindow->appendCommand(formatCommand(m_settings.p4Command(), actualArgs)); + + if (flags & ShowBusyCursor) + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + const PerforceResponse response = (flags & RunFullySynchronous) ? + fullySynchronousProcess(workingDir, actualArgs, flags, stdInput, outputCodec) : + synchronousProcess(workingDir, actualArgs, flags, stdInput, outputCodec); + + if (flags & ShowBusyCursor) + QApplication::restoreOverrideCursor(); + if (response.error) { if (Perforce::Constants::debug) qDebug() << response.message; - if (logFlags & ErrorToWindow) + if (flags & ErrorToWindow) outputWindow->appendError(response.message); } return response; @@ -871,68 +1073,47 @@ Core::IEditor * PerforcePlugin::showOutputInEditor(const QString& title, const Q return ie; } -QStringList PerforcePlugin::environment() const +void PerforcePlugin::slotSubmitDiff(const QStringList &files) { - QStringList newEnv = QProcess::systemEnvironment(); - const QString name = "P4DIFF"; - for (int i = 0; i < newEnv.count(); ++i) { - if (newEnv.at(i).startsWith(name)) { - newEnv.removeAt(i); - return newEnv; - } - } - return newEnv; -} - -void PerforcePlugin::slotDiff(const QStringList &files) -{ - p4Diff(files); + p4Diff(m_commitWorkingDirectory, files); } -void PerforcePlugin::p4Diff(const QStringList &files, QString diffname) +void PerforcePlugin::p4Diff(const QString &workingDir, const QStringList &files) { - Core::IEditor *editor = 0; - bool displayInEditor = true; Core::IEditor *existingEditor = 0; - QTextCodec *codec = files.empty() ? static_cast<QTextCodec *>(0) : VCSBase::VCSBaseEditor::getCodec(files.front()); - if (Perforce::Constants::debug) - qDebug() << Q_FUNC_INFO << files << debugCodec(codec); - - // diff of a single file? re-use an existing view if possible to support the common - // usage pattern of continuously changing and diffing a file - if (files.count() == 1) { - const QString fileName = files.at(0); - if (diffname.isEmpty()) { - const QFileInfo fi(fileName); - diffname = fi.fileName(); - } - foreach (Core::IEditor *ed, Core::EditorManager::instance()->openedEditors()) { - if (ed->file()->property("originalFileName").toString() == fileName) { - existingEditor = ed; - displayInEditor = false; - break; - } + QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(workingDir, files); + const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, files); + const QString source = VCSBase::VCSBaseEditor::getSource(workingDir, files); + + // Reuse existing editors for that id + foreach (Core::IEditor *ed, Core::EditorManager::instance()->openedEditors()) { + if (ed->file()->property("originalFileName").toString() == id) { + existingEditor = ed; + break; } } - - const PerforceResponse result = runP4Cmd(QStringList() << QLatin1String("diff") << QLatin1String("-du"), files, CommandToWindow|StdErrToWindow|ErrorToWindow, codec); + // Split arguments according to size + QStringList args; + args << QLatin1String("diff") << QLatin1String("-du"); + QStringList extraArgs; + if (files.size() > 1) { + extraArgs = files; + } else { + args.append(files); + } + const unsigned flags = CommandToWindow|StdErrToWindow|ErrorToWindow|OverrideDiffEnvironment; + const PerforceResponse result = runP4Cmd(workingDir, args, flags, + extraArgs, QByteArray(), codec); if (result.error) return; - if (displayInEditor) - editor = showOutputInEditor(tr("p4 diff %1").arg(diffname), result.stdOut, VCSBase::DiffOutput, codec); - - - if (files.count() == 1) { - if (displayInEditor && editor != 0) { - editor->file()->setProperty("originalFileName", files.at(0)); - } else if (!displayInEditor && existingEditor) { - if (existingEditor) { - existingEditor->createNew(result.stdOut); - Core::EditorManager::instance()->activateEditor(existingEditor); - } - } + if (existingEditor) { + existingEditor->createNew(result.stdOut); + Core::EditorManager::instance()->activateEditor(existingEditor); + } else { + Core::IEditor *editor = showOutputInEditor(tr("p4 diff %1").arg(id), result.stdOut, VCSBase::DiffOutput, codec); + editor->file()->setProperty("originalFileName", id); } } @@ -941,7 +1122,8 @@ void PerforcePlugin::describe(const QString & source, const QString &n) QTextCodec *codec = source.isEmpty() ? static_cast<QTextCodec *>(0) : VCSBase::VCSBaseEditor::getCodec(source); QStringList args; args << QLatin1String("describe") << QLatin1String("-du") << n; - const PerforceResponse result = runP4Cmd(args, QStringList(), CommandToWindow|StdErrToWindow|ErrorToWindow, codec); + const PerforceResponse result = runP4Cmd(m_settings.topLevel(), args, CommandToWindow|StdErrToWindow|ErrorToWindow, + QStringList(), QByteArray(), codec); if (!result.error) showOutputInEditor(tr("p4 describe %1").arg(n), result.stdOut, VCSBase::DiffOutput, codec); } @@ -958,6 +1140,7 @@ void PerforcePlugin::cleanCommitMessageFile() if (!m_commitMessageFileName.isEmpty()) { QFile::remove(m_commitMessageFileName); m_commitMessageFileName.clear(); + m_commitWorkingDirectory.clear(); } } @@ -974,164 +1157,98 @@ bool PerforcePlugin::submitEditorAboutToClose(VCSBase::VCSBaseSubmitEditor *subm const PerforceSubmitEditor *perforceEditor = qobject_cast<PerforceSubmitEditor *>(submitEditor); if (!fileIFace || !perforceEditor) return true; - const QFileInfo editorFile(fileIFace->fileName()); - const QFileInfo changeFile(m_commitMessageFileName); - if (editorFile.absoluteFilePath() == changeFile.absoluteFilePath()) { - // Prompt the user. Force a prompt unless submit was actually invoked (that - // is, the editor was closed or shutdown). - bool wantsPrompt = m_settings.promptToSubmit(); - const VCSBase::VCSBaseSubmitEditor::PromptSubmitResult answer = + // Prompt the user. Force a prompt unless submit was actually invoked (that + // is, the editor was closed or shutdown). + bool wantsPrompt = m_settings.promptToSubmit(); + const VCSBase::VCSBaseSubmitEditor::PromptSubmitResult answer = perforceEditor->promptSubmit(tr("Closing p4 Editor"), tr("Do you want to submit this change list?"), tr("The commit message check failed. Do you want to submit this change list"), &wantsPrompt, !m_submitActionTriggered); - m_submitActionTriggered = false; + m_submitActionTriggered = false; - if (answer == VCSBase::VCSBaseSubmitEditor::SubmitCanceled) - return false; + if (answer == VCSBase::VCSBaseSubmitEditor::SubmitCanceled) + return false; - // Set without triggering the checking mechanism - if (wantsPrompt != m_settings.promptToSubmit()) { - m_settings.setPromptToSubmit(wantsPrompt); - m_settings.toSettings(Core::ICore::instance()->settings()); - } - Core::FileManager *fileManager = Core::ICore::instance()->fileManager(); - fileManager->blockFileChange(fileIFace); - fileIFace->save(); - fileManager->unblockFileChange(fileIFace); - if (answer == VCSBase::VCSBaseSubmitEditor::SubmitConfirmed) { - QFile commitMessageFile(m_commitMessageFileName); - if (!commitMessageFile.open(QIODevice::ReadOnly|QIODevice::Text)) { - VCSBase::VCSBaseOutputWindow::instance()->appendError(tr("Cannot open temporary file.")); - return false; - } - QByteArray change = commitMessageFile.readAll(); - commitMessageFile.close(); - QString errorMessage; - if (!checkP4Configuration(&errorMessage)) { - VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage); - return false; - } - QProcess proc; - proc.setEnvironment(environment()); - - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - QStringList submitArgs = m_settings.basicP4Args(); - submitArgs << QLatin1String("submit") << QLatin1String("-i"); - VCSBase::VCSBaseOutputWindow::instance()->appendCommand(formatCommand(m_settings.p4Command(), submitArgs)); - proc.start(m_settings.p4Command(),submitArgs); - if (!proc.waitForStarted(p4Timeout)) { - VCSBase::VCSBaseOutputWindow::instance()->appendError(tr("Cannot execute p4 submit.")); - QApplication::restoreOverrideCursor(); - return false; - } - proc.write(change); - proc.closeWriteChannel(); - - if (!proc.waitForFinished()) { - VCSBase::VCSBaseOutputWindow::instance()->appendError(tr("Cannot execute p4 submit.")); - QApplication::restoreOverrideCursor(); - return false; - } - const int exitCode = proc.exitCode(); - if (exitCode) { - VCSBase::VCSBaseOutputWindow::instance()->appendError(tr("p4 submit failed (exit code %1).").arg(exitCode)); - QApplication::restoreOverrideCursor(); - return false; - } - const QString output = QString::fromLocal8Bit(proc.readAll()); - VCSBase::VCSBaseOutputWindow::instance()->append(output); - if (output.contains(QLatin1String("Out of date files must be resolved or reverted)"))) { - QMessageBox::warning(submitEditor->widget(), tr("Pending change"), tr("Could not submit the change, because your workspace was out of date. Created a pending submit instead.")); - } - QApplication::restoreOverrideCursor(); - } + // Set without triggering the checking mechanism + if (wantsPrompt != m_settings.promptToSubmit()) { + m_settings.setPromptToSubmit(wantsPrompt); + m_settings.toSettings(Core::ICore::instance()->settings()); + } + Core::FileManager *fileManager = Core::ICore::instance()->fileManager(); + fileManager->blockFileChange(fileIFace); + fileIFace->save(); + fileManager->unblockFileChange(fileIFace); + if (answer == VCSBase::VCSBaseSubmitEditor::SubmitDiscarded) { cleanCommitMessageFile(); + return true; + } + // Pipe file into p4 submit -i + QFile commitMessageFile(m_commitMessageFileName); + if (!commitMessageFile.open(QIODevice::ReadOnly|QIODevice::Text)) { + VCSBase::VCSBaseOutputWindow::instance()->appendError(tr("Cannot open temporary file.")); + return false; } - return true; -} -void PerforcePlugin::openFiles(const QStringList &files) -{ - Core::EditorManager *em = Core::EditorManager::instance(); - foreach (const QString &s, files) - em->openEditor(clientFilePath(s)); - em->ensureEditorManagerVisible(); + const QByteArray changeDescription = commitMessageFile.readAll(); + commitMessageFile.close(); + QStringList submitArgs; + submitArgs << QLatin1String("submit") << QLatin1String("-i"); + const PerforceResponse submitResponse = runP4Cmd(m_settings.topLevelSymLinkTarget(), submitArgs, + RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow|ShowBusyCursor, + QStringList(), changeDescription); + if (submitResponse.error) { + VCSBase::VCSBaseOutputWindow::instance()->appendError(tr("p4 submit failed: %1").arg(submitResponse.message)); + return false; + } + VCSBase::VCSBaseOutputWindow::instance()->append(submitResponse.stdOut); + if (submitResponse.stdOut.contains(QLatin1String("Out of date files must be resolved or reverted)"))) + QMessageBox::warning(submitEditor->widget(), tr("Pending change"), tr("Could not submit the change, because your workspace was out of date. Created a pending submit instead.")); + + cleanCommitMessageFile(); + return true; } QString PerforcePlugin::clientFilePath(const QString &serverFilePath) { - if (!checkP4Configuration()) - return QString(); + QTC_ASSERT(m_settings.isValid(), return QString()) - QApplication::setOverrideCursor(Qt::WaitCursor); - QProcess proc; - proc.setEnvironment(environment()); - proc.start(m_settings.p4Command(), - m_settings.basicP4Args() << QLatin1String("fstat") << serverFilePath); - - QString path; - if (proc.waitForFinished(3000)) { - QString output = QString::fromUtf8(proc.readAllStandardOutput()); - if (!output.isEmpty()) { - QRegExp r(QLatin1String("\\.\\.\\.\\sclientFile\\s(.+)\n")); - r.setMinimal(true); - if (r.indexIn(output) != -1) - path = r.cap(1).trimmed(); - } - } - QApplication::restoreOverrideCursor(); + QStringList args; + args << QLatin1String("fstat") << serverFilePath; + const PerforceResponse response = runP4Cmd(m_settings.topLevelSymLinkTarget(), args, + ShowBusyCursor|RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow); + if (response.error) + return QString::null; + + QRegExp r(QLatin1String("\\.\\.\\.\\sclientFile\\s(.+)\n")); + r.setMinimal(true); + const QString path = r.indexIn(response.stdOut) != -1 ? r.cap(1).trimmed() : QString(); + if (Perforce::Constants::debug) + qDebug() << "clientFilePath" << serverFilePath << path; return path; } -QString PerforcePlugin::currentFileName() +QString PerforcePlugin::pendingChangesData() { - QString fileName = Core::ICore::instance()->fileManager()->currentFile(); + QTC_ASSERT(m_settings.isValid(), return QString()) - // TODO: Use FileManager::fixPath - const QFileInfo fileInfo(fileName); - if (fileInfo.exists()) - fileName = fileInfo.absoluteFilePath(); - fileName = QDir::toNativeSeparators(fileName); - return fileName; -} - -bool PerforcePlugin::checkP4Configuration(QString *errorMessage /* = 0 */) const -{ - if (m_settings.isValid()) - return true; - if (errorMessage) - *errorMessage = tr("Invalid configuration: %1").arg(m_settings.errorString()); - return false; -} + QStringList args = QStringList(QLatin1String("info")); + const PerforceResponse userResponse = runP4Cmd(m_settings.topLevelSymLinkTarget(), args, + RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow); + if (userResponse.error) + return QString::null; -QString PerforcePlugin::pendingChangesData() -{ - QString data; - if (!checkP4Configuration()) - return data; - - QString user; - QProcess proc; - proc.setEnvironment(environment()); - proc.start(m_settings.p4Command(), - m_settings.basicP4Args() << QLatin1String("info")); - if (proc.waitForFinished(3000)) { - QString output = QString::fromUtf8(proc.readAllStandardOutput()); - if (!output.isEmpty()) { - QRegExp r(QLatin1String("User\\sname:\\s(\\S+)\\s*\n")); - r.setMinimal(true); - if (r.indexIn(output) != -1) - user = r.cap(1).trimmed(); - } - } + QRegExp r(QLatin1String("User\\sname:\\s(\\S+)\\s*\n")); + QTC_ASSERT(r.isValid(), return QString()) + r.setMinimal(true); + const QString user = r.indexIn(userResponse.stdOut) != -1 ? r.cap(1).trimmed() : QString::null; if (user.isEmpty()) - return data; - proc.start(m_settings.p4Command(), - m_settings.basicP4Args() << QLatin1String("changes") << QLatin1String("-s") << QLatin1String("pending") << QLatin1String("-u") << user); - if (proc.waitForFinished(3000)) - data = QString::fromUtf8(proc.readAllStandardOutput()); - return data; + return QString::null; + args.clear(); + args << QLatin1String("changes") << QLatin1String("-s") << QLatin1String("pending") << QLatin1String("-u") << user; + const PerforceResponse dataResponse = runP4Cmd(m_settings.topLevelSymLinkTarget(), args, + RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow); + return dataResponse.error ? QString::null : dataResponse.stdOut; } PerforcePlugin::~PerforcePlugin() @@ -1147,10 +1264,17 @@ void PerforcePlugin::setSettings(const Settings &newSettings) { if (newSettings != m_settings.settings()) { m_settings.setSettings(newSettings); + m_managedDirectoryCache.clear(); m_settings.toSettings(Core::ICore::instance()->settings()); + getTopLevel(); } } +static inline QString msgWhereFailed(const QString & file, const QString &why) +{ + return PerforcePlugin::tr("Error running \"where\" on %1: %2").arg(file, why); +} + // Map a perforce name "//xx" to its real name in the file system QString PerforcePlugin::fileNameFromPerforceName(const QString& perforceName, QString *errorMessage) const @@ -1159,28 +1283,29 @@ QString PerforcePlugin::fileNameFromPerforceName(const QString& perforceName, if (!perforceName.startsWith(QLatin1String("//"))) return perforceName; // "where" remaps the file to client file tree - QProcess proc; - QStringList args(m_settings.basicP4Args()); + QStringList args; args << QLatin1String("where") << perforceName; - proc.start(m_settings.p4Command(), args); - if (!proc.waitForFinished()) { - *errorMessage = tr("Timeout waiting for \"where\" (%1).").arg(perforceName); - return QString(); + const PerforceResponse response = runP4Cmd(m_settings.topLevelSymLinkTarget(), args, + RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow); + if (response.error) { + *errorMessage = msgWhereFailed(perforceName, response.message); + return QString::null; } - QString output = QString::fromLocal8Bit(proc.readAllStandardOutput()); + QString output = response.stdOut; if (output.endsWith(QLatin1Char('\r'))) output.chop(1); if (output.endsWith(QLatin1Char('\n'))) output.chop(1); if (output.isEmpty()) { - *errorMessage = tr("Error running \"where\" on %1: The file is not mapped").arg(perforceName); + *errorMessage = msgWhereFailed(perforceName, tr("The file is not mapped")); return QString(); } - const QString rc = output.mid(output.lastIndexOf(QLatin1Char(' ')) + 1); + const QString p4fileSpec = output.mid(output.lastIndexOf(QLatin1Char(' ')) + 1); + const QString rc = m_settings.mapToFileSystem(p4fileSpec); if (Perforce::Constants::debug) - qDebug() << "fileNameFromPerforceName" << perforceName << rc; + qDebug() << "fileNameFromPerforceName" << perforceName << p4fileSpec << rc; return rc; } @@ -1195,5 +1320,35 @@ PerforceVersionControl *PerforcePlugin::perforceVersionControl() const return static_cast<PerforceVersionControl *>(versionControl()); } -Q_EXPORT_PLUGIN(PerforcePlugin) +void PerforcePlugin::slotTopLevelFound(const QString &t) +{ + m_settings.setTopLevel(t); + VCSBase::VCSBaseOutputWindow::instance()->appendSilently(tr("Perforce repository: %1").arg(t)); + if (Perforce::Constants::debug) + qDebug() << "P4: " << t; +} + +void PerforcePlugin::slotTopLevelFailed(const QString &errorMessage) +{ + VCSBase::VCSBaseOutputWindow::instance()->appendError(tr("Perforce: Unable to determine the repository: %1").arg(errorMessage)); + if (Perforce::Constants::debug) + qDebug() << errorMessage; +} + +void PerforcePlugin::getTopLevel() +{ + // Run a new checker + if (m_settings.p4Command().isEmpty()) + return; + PerforceChecker *checker = new PerforceChecker(this); + connect(checker, SIGNAL(failed(QString)), this, SLOT(slotTopLevelFailed(QString))); + connect(checker, SIGNAL(failed(QString)), checker, SLOT(deleteLater())); + connect(checker, SIGNAL(succeeded(QString)), this, SLOT(slotTopLevelFound(QString))); + connect(checker, SIGNAL(succeeded(QString)),checker, SLOT(deleteLater())); + checker->start(m_settings.p4Command(), m_settings.commonP4Arguments(QString()), 30000); +} + +} +} +Q_EXPORT_PLUGIN(Perforce::Internal::PerforcePlugin) diff --git a/src/plugins/perforce/perforceplugin.h b/src/plugins/perforce/perforceplugin.h index 2aba09194c0c03df54e4a2bf7733698f468f786b..ca184caef21b6cdd9dd248d65cbb9a1958203db1 100644 --- a/src/plugins/perforce/perforceplugin.h +++ b/src/plugins/perforce/perforceplugin.h @@ -35,16 +35,18 @@ #include <coreplugin/editormanager/ieditorfactory.h> #include <coreplugin/iversioncontrol.h> #include <vcsbase/vcsbaseplugin.h> -#include <projectexplorer/projectexplorer.h> #include <QtCore/QObject> #include <QtCore/QProcess> #include <QtCore/QStringList> +#include <QtCore/QSharedPointer> +#include <QtCore/QHash> QT_BEGIN_NAMESPACE class QFile; class QAction; class QTextCodec; +class QTemporaryFile; QT_END_NAMESPACE namespace Utils { @@ -58,7 +60,10 @@ class PerforceVersionControl; struct PerforceResponse { + PerforceResponse(); + bool error; + int exitCode; QString stdOut; QString stdErr; QString message; @@ -75,13 +80,13 @@ public: bool initialize(const QStringList &arguments, QString *error_message); void extensionsInitialized(); - bool managesDirectory(const QString &directory) const; - QString findTopLevelForDirectory(const QString &directory) const; - bool vcsOpen(const QString &fileName); - bool vcsAdd(const QString &fileName); - bool vcsDelete(const QString &filename); + bool managesDirectory(const QString &directory); + QString findTopLevelForDirectory(const QString &directory); + bool vcsOpen(const QString &workingDir, const QString &fileName); + bool vcsAdd(const QString &workingDir, const QString &fileName); + bool vcsDelete(const QString &workingDir, const QString &filename); - void p4Diff(const QStringList &files, QString diffname = QString()); + void p4Diff(const QString &workingDir, const QStringList &files); Core::IEditor *openPerforceSubmitEditor(const QString &fileName, const QStringList &depotFileNames); @@ -105,9 +110,11 @@ private slots: void diffCurrentFile(); void diffCurrentProject(); void updateCurrentProject(); + void revertCurrentProject(); + void revertUnchangedCurrentProject(); void updateAll(); void diffAllOpened(); - void submit(); + void startSubmitProject(); void describeChange(); void annotateCurrentFile(); void annotate(); @@ -116,55 +123,80 @@ private slots: void submitCurrentLog(); void printPendingChanges(); - void slotDiff(const QStringList &files); + void slotSubmitDiff(const QStringList &files); + void slotTopLevelFound(const QString &); + void slotTopLevelFailed(const QString &); protected: virtual void updateActions(VCSBase::VCSBasePlugin::ActionState); virtual bool submitEditorAboutToClose(VCSBase::VCSBaseSubmitEditor *submitEditor); + private: - QStringList environment() const; + typedef QHash<QString, bool> ManagedDirectoryCache; Core::IEditor *showOutputInEditor(const QString& title, const QString output, int editorType, QTextCodec *codec = 0); - // Verbosity flags for runP4Cmd. - enum RunLogFlags { CommandToWindow = 0x1, StdOutToWindow = 0x2, StdErrToWindow = 0x4, ErrorToWindow = 0x8 }; + // Flags for runP4Cmd. + enum RunFlags { CommandToWindow = 0x1, StdOutToWindow = 0x2, + StdErrToWindow = 0x4, ErrorToWindow = 0x8, + OverrideDiffEnvironment = 0x10, + // Run completely synchronously, no signals emitted + RunFullySynchronous = 0x20, + IgnoreExitCode = 0x40, + ShowBusyCursor = 0x80 + }; // args are passed as command line arguments // extra args via a tempfile and the option -x "temp-filename" - PerforceResponse runP4Cmd(const QStringList &args, + PerforceResponse runP4Cmd(const QString &workingDir, + const QStringList &args, + unsigned flags = CommandToWindow|StdErrToWindow|ErrorToWindow, const QStringList &extraArgs = QStringList(), - unsigned logFlags = CommandToWindow|StdErrToWindow|ErrorToWindow, + const QByteArray &stdInput = QByteArray(), QTextCodec *outputCodec = 0) const; - void openFiles(const QStringList &files); + inline PerforceResponse synchronousProcess(const QString &workingDir, + const QStringList &args, + unsigned flags, + const QByteArray &stdInput, + QTextCodec *outputCodec) const; + + inline PerforceResponse fullySynchronousProcess(const QString &workingDir, + const QStringList &args, + unsigned flags, + const QByteArray &stdInput, + QTextCodec *outputCodec) const; QString clientFilePath(const QString &serverFilePath); - QString currentFileName(); - bool checkP4Configuration(QString *errorMessage = 0) const; - void annotate(const QString &fileName); - void filelog(const QString &fileName); + void annotate(const QString &workingDir, const QString &fileName); + void filelog(const QString &workingDir, const QStringList &fileNames); void cleanCommitMessageFile(); bool isCommitEditorOpen() const; + QSharedPointer<QTemporaryFile> createTemporaryArgumentFile(const QStringList &extraArgs) const; + void getTopLevel(); + QString pendingChangesData(); - void updateCheckout(const QStringList &dirs = QStringList()); - inline PerforceVersionControl *perforceVersionControl() const; + void updateCheckout(const QString &workingDir = QString(), + const QStringList &dirs = QStringList()); + bool revertProject(const QString &workingDir, const QStringList &args, bool unchangedOnly); - ProjectExplorer::ProjectExplorerPlugin *m_projectExplorer; + inline PerforceVersionControl *perforceVersionControl() const; Utils::ParameterAction *m_editAction; Utils::ParameterAction *m_addAction; Utils::ParameterAction *m_deleteAction; QAction *m_openedAction; - Utils::ParameterAction *m_revertAction; - Utils::ParameterAction *m_diffCurrentAction; + Utils::ParameterAction *m_revertFileAction; + Utils::ParameterAction *m_diffFileAction; Utils::ParameterAction *m_diffProjectAction; Utils::ParameterAction *m_updateProjectAction; + Utils::ParameterAction *m_revertProjectAction; + Utils::ParameterAction *m_revertUnchangedAction; QAction *m_diffAllAction; - QAction *m_resolveAction; - QAction *m_submitAction; + Utils::ParameterAction *m_submitProjectAction; QAction *m_pendingAction; QAction *m_describeAction; Utils::ParameterAction *m_annotateCurrentAction; @@ -176,15 +208,16 @@ private: bool m_submitActionTriggered; QAction *m_diffSelectedFiles; QString m_commitMessageFileName; - + QString m_commitWorkingDirectory; + mutable QString m_tempFilePattern; QAction *m_undoAction; QAction *m_redoAction; QAction *m_menuAction; static PerforcePlugin *m_perforcePluginInstance; - QString pendingChangesData(); PerforceSettings m_settings; + ManagedDirectoryCache m_managedDirectoryCache; }; } // namespace Perforce diff --git a/src/plugins/perforce/perforcesettings.cpp b/src/plugins/perforce/perforcesettings.cpp index 8c72774a29db3ccdede8be20106ff78a679ae432..949857d4a379c7398f9a13ac672b62a862ab7d2c 100644 --- a/src/plugins/perforce/perforcesettings.cpp +++ b/src/plugins/perforce/perforcesettings.cpp @@ -28,15 +28,16 @@ **************************************************************************/ #include "perforcesettings.h" +#include "perforceplugin.h" +#include "perforceconstants.h" + +#include <utils/qtcassert.h> -#include <qtconcurrent/QtConcurrentTools> -#include <QtCore/QtConcurrentRun> #include <QtCore/QSettings> #include <QtCore/QStringList> #include <QtCore/QCoreApplication> -#include <QtCore/QProcess> - -enum { debug = 0 }; +#include <QtCore/QDir> +#include <QtCore/QFileInfo> static const char *groupC = "Perforce"; static const char *commandKeyC = "Command"; @@ -73,7 +74,7 @@ bool Settings::equals(const Settings &rhs) const && promptToSubmit == rhs.promptToSubmit; }; -QStringList Settings::basicP4Args() const +QStringList Settings::commonP4Arguments() const { if (defaultEnv) return QStringList(); @@ -87,104 +88,17 @@ QStringList Settings::basicP4Args() const return lst; } -bool Settings::check(QString *errorMessage) const -{ - return doCheck(p4Command, basicP4Args(), errorMessage); -} - -// Check on a p4 view by grepping "view -o" for mapped files -bool Settings::doCheck(const QString &binary, const QStringList &basicArgs, QString *errorMessage) -{ - errorMessage->clear(); - if (binary.isEmpty()) { - *errorMessage = QCoreApplication::translate("Perforce::Internal", "No executable specified"); - return false; - } - // List the client state and check for mapped files - QProcess p4; - QStringList args = basicArgs; - args << QLatin1String("client") << QLatin1String("-o"); - if (debug) - qDebug() << binary << args; - p4.start(binary, args); - if (!p4.waitForStarted()) { - *errorMessage = QCoreApplication::translate("Perforce::Internal", "Unable to launch \"%1\": %2").arg(binary, p4.errorString()); - return false; - } - p4.closeWriteChannel(); - const int timeOutMS = 5000; - if (!p4.waitForFinished(timeOutMS)) { - p4.kill(); - p4.waitForFinished(); - *errorMessage = QCoreApplication::translate("Perforce::Internal", "\"%1\" timed out after %2ms.").arg(binary).arg(timeOutMS); - return false; - } - if (p4.exitStatus() != QProcess::NormalExit) { - *errorMessage = QCoreApplication::translate("Perforce::Internal", "\"%1\" crashed.").arg(binary); - return false; - } - const QString stdErr = QString::fromLocal8Bit(p4.readAllStandardError()); - if (p4.exitCode()) { - *errorMessage = QCoreApplication::translate("Perforce::Internal", "\"%1\" terminated with exit code %2: %3"). - arg(binary).arg(p4.exitCode()).arg(stdErr); - return false; - - } - // List the client state and check for "View" - const QString response = QString::fromLocal8Bit(p4.readAllStandardOutput()); - if (!response.contains(QLatin1String("View:")) && !response.contains(QLatin1String("//depot/"))) { - *errorMessage = QCoreApplication::translate("Perforce::Internal", "The client does not seem to contain any mapped files."); - return false; - } - return true; -} - // --------------------PerforceSettings PerforceSettings::PerforceSettings() - : m_valid(false) { - // We do all the initialization in fromSettings } PerforceSettings::~PerforceSettings() { - // ensure that we are not still running - m_future.waitForFinished(); -} - -bool PerforceSettings::isValid() const -{ - m_future.waitForFinished(); - m_mutex.lock(); - bool valid = m_valid; - m_mutex.unlock(); - return valid; -} - -void PerforceSettings::run(QFutureInterface<void> &fi) -{ - m_mutex.lock(); - const QString executable = m_settings.p4Command; - const QStringList arguments = basicP4Args(); - m_mutex.unlock(); - - QString errorString; - const bool isValid = Settings::doCheck(executable, arguments, &errorString); - if (debug) - qDebug() << isValid << errorString; - - m_mutex.lock(); - if (executable == m_settings.p4Command && arguments == basicP4Args()) { // Check that those settings weren't changed in between - m_errorString = errorString; - m_valid = isValid; - } - m_mutex.unlock(); - fi.reportFinished(); } void PerforceSettings::fromSettings(QSettings *settings) { - m_mutex.lock(); settings->beginGroup(QLatin1String(groupC)); m_settings.p4Command = settings->value(QLatin1String(commandKeyC), defaultCommand()).toString(); m_settings.defaultEnv = settings->value(QLatin1String(defaultKeyC), true).toBool(); @@ -193,14 +107,10 @@ void PerforceSettings::fromSettings(QSettings *settings) m_settings.p4User = settings->value(QLatin1String(userKeyC), QString()).toString(); m_settings.promptToSubmit = settings->value(QLatin1String(promptToSubmitKeyC), true).toBool(); settings->endGroup(); - m_mutex.unlock(); - - m_future = QtConcurrent::run(&PerforceSettings::run, this); } void PerforceSettings::toSettings(QSettings *settings) const { - m_mutex.lock(); settings->beginGroup(QLatin1String(groupC)); settings->setValue(QLatin1String(commandKeyC), m_settings.p4Command); settings->setValue(QLatin1String(defaultKeyC), m_settings.defaultEnv); @@ -209,18 +119,13 @@ void PerforceSettings::toSettings(QSettings *settings) const settings->setValue(QLatin1String(userKeyC), m_settings.p4User); settings->setValue(QLatin1String(promptToSubmitKeyC), m_settings.promptToSubmit); settings->endGroup(); - m_mutex.unlock(); } void PerforceSettings::setSettings(const Settings &newSettings) -{ +{ if (newSettings != m_settings) { - // trigger check m_settings = newSettings; - m_mutex.lock(); - m_valid = false; - m_mutex.unlock(); - m_future = QtConcurrent::run(&PerforceSettings::run, this); + clearTopLevel(); } } @@ -264,17 +169,87 @@ void PerforceSettings::setPromptToSubmit(bool p) m_settings.promptToSubmit = p; } -QString PerforceSettings::errorString() const +QString PerforceSettings::topLevel() const { - m_mutex.lock(); - const QString rc = m_errorString; - m_mutex.unlock(); + return m_topLevel; +} + +QString PerforceSettings::topLevelSymLinkTarget() const +{ + return m_topLevelSymLinkTarget; +} + +void PerforceSettings::setTopLevel(const QString &t) +{ + if (m_topLevel == t) + return; + clearTopLevel(); + if (!t.isEmpty()) { + // Check/expand symlinks as creator always has expanded file paths + QFileInfo fi(t); + if (fi.isSymLink()) { + m_topLevel = t; + m_topLevelSymLinkTarget = QFileInfo(fi.symLinkTarget()).absoluteFilePath(); + } else { + m_topLevelSymLinkTarget = m_topLevel = t; + } + m_topLevelDir.reset(new QDir(m_topLevelSymLinkTarget)); + if (Perforce::Constants::debug) + qDebug() << "PerforceSettings::setTopLevel" << m_topLevel << m_topLevelSymLinkTarget; + } +} + +void PerforceSettings::clearTopLevel() +{ + m_topLevelDir.reset(); + m_topLevel.clear(); +} + +QString PerforceSettings::relativeToTopLevel(const QString &dir) const +{ + QTC_ASSERT(!m_topLevelDir.isNull(), return QLatin1String("../") + dir) + return m_topLevelDir->relativeFilePath(dir); +} + +QStringList PerforceSettings::relativeToTopLevelArguments(const QString &dir) const +{ + const QString relative = relativeToTopLevel(dir); + return relative.isEmpty() ? QStringList() : QStringList(relative); +} + +// Map the root part of a path: +// Calling "/home/john/foo" with old="/home", new="/user" +// results in "/user/john/foo" + +static inline QString mapPathRoot(const QString &path, + const QString &oldPrefix, + const QString &newPrefix) +{ + if (path.isEmpty() || oldPrefix.isEmpty() || newPrefix.isEmpty() || oldPrefix == newPrefix) + return path; + if (path == oldPrefix) + return newPrefix; + if (path.startsWith(oldPrefix)) + return newPrefix + path.right(path.size() - oldPrefix.size()); + return path; +} + +QStringList PerforceSettings::commonP4Arguments(const QString &workingDir) const +{ + QStringList rc; + if (!workingDir.isEmpty()) { + /* Determine the -d argument for the working directory for matching relative paths. + * It is is below the toplevel, replace top level portion by exact specification. */ + rc << QLatin1String("-d") + << QDir::toNativeSeparators(mapPathRoot(workingDir, m_topLevelSymLinkTarget, m_topLevel)); + } + rc.append(m_settings.commonP4Arguments()); return rc; } -QStringList PerforceSettings::basicP4Args() const +QString PerforceSettings::mapToFileSystem(const QString &perforceFilePath) const { - return m_settings.basicP4Args(); + return mapPathRoot(perforceFilePath, m_topLevel, m_topLevelSymLinkTarget); } } // Internal diff --git a/src/plugins/perforce/perforcesettings.h b/src/plugins/perforce/perforcesettings.h index 06c1b46f09c8ab2fab415b5bbcc47cf461efb99b..8b6e3897cd27634ced76c9b3b1b5f861ee9ec326 100644 --- a/src/plugins/perforce/perforcesettings.h +++ b/src/plugins/perforce/perforcesettings.h @@ -31,10 +31,11 @@ #define PERFOCESETTINGS_H #include <QtCore/QString> -#include <QtCore/QFuture> +#include <QtCore/QScopedPointer> QT_BEGIN_NAMESPACE class QSettings; +class QDir; QT_END_NAMESPACE namespace Perforce { @@ -43,10 +44,13 @@ namespace Internal { struct Settings { Settings(); bool equals(const Settings &s) const; - QStringList basicP4Args() const; + QStringList commonP4Arguments() const; - bool check(QString *errorMessage) const; - static bool doCheck(const QString &binary, const QStringList &basicArgs, QString *errorMessage); + // Checks. On success, errorMessage will contains the client root. + bool check(QString *repositoryRoot /* = 0*/, QString *errorMessage) const; + static bool doCheck(const QString &binary, const QStringList &basicArgs, + QString *repositoryRoot /* = 0 */, + QString *errorMessage); QString p4Command; QString p4Port; @@ -60,22 +64,48 @@ struct Settings { inline bool operator==(const Settings &s1, const Settings &s2) { return s1.equals(s2); } inline bool operator!=(const Settings &s1, const Settings &s2) { return !s1.equals(s2); } -// PerforceSettings: Aggregates settings struct and contains a sophisticated -// background check invoked on setSettings() to figure out whether the p4 -// configuration is actually valid (disabling it when invalid to save time -// when updating actions. etc.) +/* PerforceSettings: Aggregates settings struct and toplevel directory + * which is determined externally by background checks and provides a convenience + * for determining the common arguments. + * Those must contain (apart from server connection settings) the working directory + * with the "-d" option. This is because the p4 command line client detects its path + * from the PWD environment variable which breaks relative paths since that is set by + * the shell running Creator and is not necessarily that of the working directory + * (see p4 documentation). + * An additional complication is that the repository might be a symbolic link on Unix, + * say "$HOME/dev" linked to "/depot". If the p4 client specification contains + * "$HOME/dev", paths containing "/depot" will be refused as "not under client's view" by + * p4. This is why the client root portion of working directory must be mapped for the + * "-d" option, so that running p4 in "/depot/dev/foo" results in "-d $HOME/dev/foo". */ class PerforceSettings { + Q_DISABLE_COPY(PerforceSettings); public: PerforceSettings(); ~PerforceSettings(); + + inline bool isValid() const { return !m_topLevel.isEmpty(); } + void fromSettings(QSettings *settings); void toSettings(QSettings *) const; void setSettings(const Settings &s); Settings settings() const; - bool isValid() const; + QString topLevel() const; + QString topLevelSymLinkTarget() const; + + void setTopLevel(const QString &); + + // Return relative path to top level. Returns "" if it is the same directory, + // ".." if it is not within. + QString relativeToTopLevel(const QString &dir) const; + // Return argument list relative to top level (empty meaning, + // it is the same directory). + QStringList relativeToTopLevelArguments(const QString &dir) const; + + // Map p4 path back to file system in case of a symlinked top-level + QString mapToFileSystem(const QString &perforceFilePath) const; QString p4Command() const; QString p4Port() const; @@ -85,21 +115,17 @@ public: bool promptToSubmit() const; void setPromptToSubmit(bool p); - QStringList basicP4Args() const; - - // Error code of last check - QString errorString() const; + // Return basic arguments, including -d and server connection parameters. + QStringList commonP4Arguments(const QString &workingDir) const; private: - void run(QFutureInterface<void> &fi); - - mutable QFuture<void> m_future; - mutable QMutex m_mutex; + inline QStringList workingDirectoryArguments(const QString &workingDir) const; + void clearTopLevel(); Settings m_settings; - QString m_errorString; - bool m_valid; - Q_DISABLE_COPY(PerforceSettings); + QString m_topLevel; + QString m_topLevelSymLinkTarget; + QScopedPointer<QDir> m_topLevelDir; }; } // Internal diff --git a/src/plugins/perforce/perforceversioncontrol.cpp b/src/plugins/perforce/perforceversioncontrol.cpp index f5424082810328d52e7abcc168b5fb4ce4df4d78..0fe7bb6a07c27ba1c21c295e0eb82fc3aa289176 100644 --- a/src/plugins/perforce/perforceversioncontrol.cpp +++ b/src/plugins/perforce/perforceversioncontrol.cpp @@ -29,6 +29,10 @@ #include "perforceversioncontrol.h" #include "perforceplugin.h" +#include "perforceconstants.h" + +#include <QtCore/QFileInfo> +#include <QtCore/QDebug> namespace Perforce { namespace Internal { @@ -38,7 +42,7 @@ PerforceVersionControl::PerforceVersionControl(PerforcePlugin *plugin) : m_plugin(plugin) { } - + QString PerforceVersionControl::name() const { return QLatin1String("perforce"); @@ -58,27 +62,36 @@ bool PerforceVersionControl::supportsOperation(Operation operation) const bool PerforceVersionControl::vcsOpen(const QString &fileName) { - return m_plugin->vcsOpen(fileName); + const QFileInfo fi(fileName); + return m_plugin->vcsOpen(fi.absolutePath(), fi.fileName()); } bool PerforceVersionControl::vcsAdd(const QString &fileName) { - return m_plugin->vcsAdd(fileName); + const QFileInfo fi(fileName); + return m_plugin->vcsAdd(fi.absolutePath(), fi.fileName()); } bool PerforceVersionControl::vcsDelete(const QString &fileName) { - return m_plugin->vcsDelete(fileName); + const QFileInfo fi(fileName); + return m_plugin->vcsDelete(fi.absolutePath(), fi.fileName()); } bool PerforceVersionControl::managesDirectory(const QString &directory) const { - return m_plugin->managesDirectory(directory); + const bool rc = m_plugin->managesDirectory(directory); + if (Perforce::Constants::debug) + qDebug() << "managesDirectory" << directory << rc; + return rc; } QString PerforceVersionControl::findTopLevelForDirectory(const QString &directory) const { - return m_plugin->findTopLevelForDirectory(directory); + const QString rc = m_plugin->findTopLevelForDirectory(directory); + if (Perforce::Constants::debug) + qDebug() << "findTopLevelForDirectory" << directory << rc; + return rc; } void PerforceVersionControl::emitRepositoryChanged(const QString &s) diff --git a/src/plugins/perforce/settingspage.cpp b/src/plugins/perforce/settingspage.cpp index 41a5239bc3f89dae669970b724b7a375201f785e..ede4c7012f60cd51063d85ab4cfc70e07e45d27e 100644 --- a/src/plugins/perforce/settingspage.cpp +++ b/src/plugins/perforce/settingspage.cpp @@ -30,6 +30,7 @@ #include "settingspage.h" #include "perforcesettings.h" #include "perforceplugin.h" +#include "perforcechecker.h" #include <vcsbase/vcsbaseconstants.h> @@ -52,15 +53,24 @@ SettingsPageWidget::SettingsPageWidget(QWidget *parent) : void SettingsPageWidget::slotTest() { - QString message; - QApplication::setOverrideCursor(Qt::BusyCursor); - setStatusText(true, tr("Testing...")); - QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - const bool ok = settings().check(&message); - QApplication::restoreOverrideCursor(); - if (ok) - message = tr("Test succeeded."); - setStatusText(ok, message); + if (!m_checker) { + m_checker = new PerforceChecker(this); + m_checker->setUseOverideCursor(true); + connect(m_checker.data(), SIGNAL(failed(QString)), this, SLOT(setStatusError(QString))); + connect(m_checker.data(), SIGNAL(succeeded(QString)), this, SLOT(testSucceeded(QString))); + } + + if (m_checker->isRunning()) + return; + + setStatusText(tr("Testing...")); + const Settings s = settings(); + m_checker->start(s.p4Command, s.commonP4Arguments(), 10000); +} + +void SettingsPageWidget::testSucceeded(const QString &repo) +{ + setStatusText(tr("Test succeeded (%1).").arg(repo)); } Settings SettingsPageWidget::settings() const @@ -83,13 +93,17 @@ void SettingsPageWidget::setSettings(const PerforceSettings &s) m_ui.clientLineEdit->setText(s.p4Client()); m_ui.userLineEdit->setText(s.p4User()); m_ui.promptToSubmitCheckBox->setChecked(s.promptToSubmit()); - const QString errorString = s.errorString(); - setStatusText(errorString.isEmpty(), errorString); } -void SettingsPageWidget::setStatusText(bool ok, const QString &t) +void SettingsPageWidget::setStatusText(const QString &t) +{ + m_ui.errorLabel->setStyleSheet(QString::null); + m_ui.errorLabel->setText(t); +} + +void SettingsPageWidget::setStatusError(const QString &t) { - m_ui.errorLabel->setStyleSheet(ok ? QString() : QString(QLatin1String("background-color: red"))); + m_ui.errorLabel->setStyleSheet(QLatin1String("background-color: red")); m_ui.errorLabel->setText(t); } diff --git a/src/plugins/perforce/settingspage.h b/src/plugins/perforce/settingspage.h index 3836d2cc1ed067c22de1feb290ab55a5fc7edbbc..a0c5db04084a9569bf8285401d33e6736410aa2f 100644 --- a/src/plugins/perforce/settingspage.h +++ b/src/plugins/perforce/settingspage.h @@ -41,6 +41,7 @@ namespace Perforce { namespace Internal { class PerforceSettings; +class PerforceChecker; struct Settings; class SettingsPageWidget : public QWidget { @@ -55,11 +56,14 @@ public: private slots: void slotTest(); + void setStatusText(const QString &); + void setStatusError(const QString &); + void testSucceeded(const QString &repo); private: - void setStatusText(bool ok, const QString &); Ui::SettingsPage m_ui; + QPointer<PerforceChecker> m_checker; }; class SettingsPage : public Core::IOptionsPage diff --git a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp index dba5cc254180574bbb6c784a1ffe0ae52528a471..47b07aeca92a3fd02df90caaa7a8602e48e8e5d5 100644 --- a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp +++ b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp @@ -204,7 +204,7 @@ void VCSBaseSubmitEditor::createUserFields(const QString &fieldConfigFile) Utils::SubmitFieldWidget *fieldWidget = new Utils::SubmitFieldWidget; connect(fieldWidget, SIGNAL(browseButtonClicked(int,QString)), - this, SLOT(slotSetFieldNickName(int))); + this, SLOT(slotSetFieldNickName(int))); fieldWidget->setCompleter(completer); fieldWidget->setAllowDuplicateFields(true); fieldWidget->setHasBrowseButton(true); @@ -557,7 +557,7 @@ bool VCSBaseSubmitEditor::runSubmitMessageCheckScript(const QString &checkScript if (!checkProcess.waitForFinished()) { *errorMessage = tr("The check script '%1' could not be run: %2").arg(checkScript, checkProcess.errorString()); return false; - } + } const int exitCode = checkProcess.exitCode(); if (exitCode != 0) { *errorMessage = QString::fromLocal8Bit(checkProcess.readAllStandardError()); @@ -581,32 +581,23 @@ QIcon VCSBaseSubmitEditor::submitIcon() // Compile a list if files in the current projects. TODO: Recurse down qrc files? QStringList VCSBaseSubmitEditor::currentProjectFiles(bool nativeSeparators, QString *name) { - using namespace ProjectExplorer; if (name) name->clear(); - ProjectExplorerPlugin *pe = ProjectExplorerPlugin::instance(); - if (!pe) - return QStringList(); - QStringList files; - if (const Project *currentProject = pe->currentProject()) { - files << currentProject->files(Project::ExcludeGeneratedFiles); - if (name) - *name = currentProject->name(); - } else { - if (const SessionManager *session = pe->session()) { + + if (ProjectExplorer::ProjectExplorerPlugin *pe = ProjectExplorer::ProjectExplorerPlugin::instance()) { + if (const ProjectExplorer::Project *currentProject = pe->currentProject()) { + QStringList files = currentProject->files(ProjectExplorer::Project::ExcludeGeneratedFiles); if (name) - *name = session->file()->fileName(); - const QList<Project *> projects = session->projects(); - foreach (Project *project, projects) - files << project->files(Project::ExcludeGeneratedFiles); + *name = currentProject->name(); + if (nativeSeparators && !files.empty()) { + const QStringList::iterator end = files.end(); + for (QStringList::iterator it = files.begin(); it != end; ++it) + *it = QDir::toNativeSeparators(*it); + } + return files; } } - if (nativeSeparators && !files.empty()) { - const QStringList::iterator end = files.end(); - for (QStringList::iterator it = files.begin(); it != end; ++it) - *it = QDir::toNativeSeparators(*it); - } - return files; + return QStringList(); } // Reduce a list of untracked files reported by a VCS down to the files