Commit 8097879d authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

VCS: Introduce Base class for VCS plugins, use in git.

Fixes:
- Cannot diff a file that does not belong to a project
- Cannot commit when a temporary diff/log view is open
  due to the current file pointing to a temporary directory
- git's project-related actions not passing the correct
  relative path.

Implementation:
- Centralize code to listen for Qt Creator's relevant state changes
  in VCSBasePlugin, dispatching the changes to the instances affected.
  (avoiding multiple invocations of searches/QFileInfo on current).
- Do the same for the corelistener catching closing SubmitEditors.
- Introduce VCSBasePluginState representing the relevant state
  (current file/project).
- Call git with working directory set and relative arguments
- Remove setEnabled/isEnabled() logic of IVersionControl
- Pass toplevel from VCSManager to avoid duplicate searches.
parent 8d6b4e51
......@@ -48,13 +48,6 @@ public:
virtual QString name() const = 0;
virtual bool isEnabled() const = 0;
/*!
* Enable the VCS, that is, make its menu actions visible.
*/
virtual void setEnabled(bool enabled) = 0;
/*!
* Returns whether files in this directory should be managed with this
* version control.
......
......@@ -46,15 +46,18 @@ enum { debug = 0 };
namespace Core {
typedef QList<IVersionControl *> VersionControlList;
typedef QMap<QString, IVersionControl *> VersionControlCache;
static inline VersionControlList allVersionControls()
{
return ExtensionSystem::PluginManager::instance()->getObjects<IVersionControl>();
}
// ---- VCSManagerPrivate
// ---- VCSManagerPrivate:
// Maintains a cache of top-level directory->version control.
struct VCSManagerPrivate {
QMap<QString, IVersionControl *> m_cachedMatches;
VersionControlCache m_cachedMatches;
};
VCSManager::VCSManager(QObject *parent) :
......@@ -79,57 +82,48 @@ void VCSManager::extensionsInitialized()
}
}
void VCSManager::setVCSEnabled(const QString &directory)
{
if (debug)
qDebug() << Q_FUNC_INFO << directory;
IVersionControl* managingVCS = findVersionControlForDirectory(directory);
const VersionControlList versionControls = allVersionControls();
foreach (IVersionControl *versionControl, versionControls) {
const bool newEnabled = versionControl == managingVCS;
if (newEnabled != versionControl->isEnabled())
versionControl->setEnabled(newEnabled);
}
}
void VCSManager::setAllVCSEnabled()
{
if (debug)
qDebug() << Q_FUNC_INFO;
const VersionControlList versionControls = allVersionControls();
foreach (IVersionControl *versionControl, versionControls)
if (!versionControl->isEnabled())
versionControl->setEnabled(true);
}
IVersionControl* VCSManager::findVersionControlForDirectory(const QString &directory)
IVersionControl* VCSManager::findVersionControlForDirectory(const QString &directory,
QString *topLevelDirectory)
{
// first look into the cache, check the whole name
{
const QMap<QString, IVersionControl *>::const_iterator it = m_d->m_cachedMatches.constFind(directory);
if (it != m_d->m_cachedMatches.constEnd())
return it.value();
typedef VersionControlCache::const_iterator VersionControlCacheConstIterator;
const VersionControlCacheConstIterator cacheEnd = m_d->m_cachedMatches.constEnd();
if (topLevelDirectory)
topLevelDirectory->clear();
// First check if the directory has an entry, meaning it is a top level
const VersionControlCacheConstIterator fullPathIt = m_d->m_cachedMatches.constFind(directory);
if (fullPathIt != cacheEnd) {
if (topLevelDirectory)
*topLevelDirectory = directory;
return fullPathIt.value();
}
// Split the path, starting from top, try to find the matching repository
int pos = 0;
const QChar slash = QLatin1Char('/');
while (true) {
int index = directory.indexOf(slash, pos);
const int index = directory.indexOf(slash, pos);
if (index == -1)
break;
const QString directoryPart = directory.left(index);
QMap<QString, IVersionControl *>::const_iterator it = m_d->m_cachedMatches.constFind(directoryPart);
if (it != m_d->m_cachedMatches.constEnd())
const VersionControlCacheConstIterator it = m_d->m_cachedMatches.constFind(directoryPart);
if (it != cacheEnd) {
if (topLevelDirectory)
*topLevelDirectory = it.key();
return it.value();
pos = index+1;
}
pos = index + 1;
}
// ah nothing so ask the IVersionControls directly
// Nothing: ask the IVersionControls directly, insert the toplevel into the cache.
const VersionControlList versionControls = allVersionControls();
foreach (IVersionControl * versionControl, versionControls) {
if (versionControl->managesDirectory(directory)) {
m_d->m_cachedMatches.insert(versionControl->findTopLevelForDirectory(directory), versionControl);
const QString topLevel = versionControl->findTopLevelForDirectory(directory);
m_d->m_cachedMatches.insert(topLevel, versionControl);
if (topLevelDirectory)
*topLevelDirectory = topLevel;
return versionControl;
}
}
......@@ -152,4 +146,14 @@ bool VCSManager::showDeleteDialog(const QString &fileName)
return vc->vcsDelete(fileName);
}
CORE_EXPORT QDebug operator<<(QDebug in, const VCSManager &v)
{
QDebug nospace = in.nospace();
const VersionControlCache::const_iterator cend = v.m_d->m_cachedMatches.constEnd();
for (VersionControlCache::const_iterator it = v.m_d->m_cachedMatches.constBegin(); it != cend; ++it)
nospace << "Directory: " << it.key() << ' ' << it.value()->name() << '\n';
nospace << '\n';
return in;
}
} // namespace Core
......@@ -35,20 +35,26 @@
#include <QtCore/QString>
#include <QtCore/QObject>
QT_BEGIN_NAMESPACE
class QDebug;
QT_END_NAMESPACE
namespace Core {
struct VCSManagerPrivate;
class IVersionControl;
// The VCSManager has only one notable function:
// findVersionControlFor(), which returns the IVersionControl * for a given
// filename. Note that the VCSManager assumes that if a IVersionControl *
// manages a directory, then it also manages all the files and all the
// subdirectories.
//
// It works by asking all IVersionControl * if they manage the file, and ask
// for the topmost directory it manages. This information is cached and
// VCSManager thus knows pretty fast which IVersionControl * is responsible.
/* VCSManager:
* 1) Provides functionality for finding the IVersionControl * for a given
* filename (findVersionControlForDirectory). Note that the VCSManager assumes
* that if a IVersionControl * manages a directory, then it also manages
* all the files and all the subdirectories.
* It works by asking all IVersionControl * if they manage the file, and ask
* for the topmost directory it manages. This information is cached and
* VCSManager thus knows pretty fast which IVersionControl * is responsible.
* 2) Passes on the changes from the version controls caused by updating or
* branching repositories and routes them to its signals (repositoryChanged,
* filesChanged). */
class CORE_EXPORT VCSManager : public QObject
{
......@@ -60,19 +66,16 @@ public:
void extensionsInitialized();
IVersionControl *findVersionControlForDirectory(const QString &directory);
// Enable the VCS managing a certain directory only. This should
// be used by project manager classes.
void setVCSEnabled(const QString &directory);
// Enable all VCS.
void setAllVCSEnabled();
IVersionControl *findVersionControlForDirectory(const QString &directory,
QString *topLevelDirectory = 0);
// Shows a confirmation dialog, whether the file should also be deleted
// from revision control Calls sccDelete on the file. Returns false
// if a failure occurs
bool showDeleteDialog(const QString &fileName);
friend CORE_EXPORT QDebug operator<<(QDebug in, const VCSManager &);
signals:
void repositoryChanged(const QString &repository);
void filesChanged(const QStringList &files);
......@@ -81,6 +84,8 @@ private:
VCSManagerPrivate *m_d;
};
CORE_EXPORT QDebug operator<<(QDebug in, const VCSManager &);
} // namespace Core
#endif // VCSMANAGER_H
......@@ -44,19 +44,6 @@ QString CVSControl::name() const
return QLatin1String("cvs");
}
bool CVSControl::isEnabled() const
{
return m_enabled;
}
void CVSControl::setEnabled(bool enabled)
{
if (m_enabled != enabled) {
m_enabled = enabled;
emit enabledChanged(m_enabled);
}
}
bool CVSControl::supportsOperation(Operation operation) const
{
bool rc = true;
......
......@@ -45,9 +45,6 @@ public:
explicit CVSControl(CVSPlugin *plugin);
virtual QString name() const;
virtual bool isEnabled() const;
virtual void setEnabled(bool enabled);
virtual bool managesDirectory(const QString &directory) const;
virtual QString findTopLevelForDirectory(const QString &directory) const;
......@@ -59,9 +56,6 @@ public:
void emitRepositoryChanged(const QString &s);
void emitFilesChanged(const QStringList &l);
signals:
void enabledChanged(bool);
private:
bool m_enabled;
CVSPlugin *m_plugin;
......
......@@ -151,7 +151,7 @@ Core::IEditor* locateEditor(const char *property, const QString &entry)
CVSPlugin *CVSPlugin::m_cvsPluginInstance = 0;
CVSPlugin::CVSPlugin() :
m_versionControl(0),
VCSBase::VCSBasePlugin(QLatin1String(CVS::Constants::CVSCOMMITEDITOR_KIND)),
m_projectExplorer(0),
m_addAction(0),
m_deleteAction(0),
......@@ -168,6 +168,7 @@ CVSPlugin::CVSPlugin() :
m_submitDiffAction(0),
m_submitUndoAction(0),
m_submitRedoAction(0),
m_menuAction(0),
m_submitActionTriggered(false)
{
}
......@@ -205,10 +206,8 @@ static inline Core::Command *createSeparator(QObject *parent,
return ami->registerAction(tmpaction, id, globalcontext);
}
bool CVSPlugin::initialize(const QStringList &arguments, QString *errorMessage)
bool CVSPlugin::initialize(const QStringList & /*arguments */, QString *errorMessage)
{
Q_UNUSED(arguments);
typedef VCSBase::VCSSubmitEditorFactory<CVSSubmitEditor> CVSSubmitEditorFactory;
typedef VCSBase::VCSEditorFactory<CVSEditor> CVSEditorFactory;
using namespace Constants;
......@@ -216,21 +215,17 @@ bool CVSPlugin::initialize(const QStringList &arguments, QString *errorMessage)
using namespace Core::Constants;
using namespace ExtensionSystem;
VCSBase::VCSBasePlugin::initialize(new CVSControl(this));
m_cvsPluginInstance = this;
Core::ICore *core = Core::ICore::instance();
if (!core->mimeDatabase()->addMimeTypes(QLatin1String(":/trolltech.cvs/CVS.mimetypes.xml"), errorMessage))
return false;
m_versionControl = new CVSControl(this);
addAutoReleasedObject(m_versionControl);
if (QSettings *settings = core->settings())
m_settings.fromSettings(settings);
addAutoReleasedObject(new CoreListener(this));
addAutoReleasedObject(new SettingsPage);
addAutoReleasedObject(new CVSSubmitEditorFactory(&submitParameters));
......@@ -250,10 +245,7 @@ bool CVSPlugin::initialize(const QStringList &arguments, QString *errorMessage)
ami->createMenu(QLatin1String(CMD_ID_CVS_MENU));
cvsMenu->menu()->setTitle(tr("&CVS"));
toolsContainer->addMenu(cvsMenu);
if (QAction *ma = cvsMenu->menu()->menuAction()) {
ma->setEnabled(m_versionControl->isEnabled());
connect(m_versionControl, SIGNAL(enabledChanged(bool)), ma, SLOT(setVisible(bool)));
}
m_menuAction = cvsMenu->menu()->menuAction();
QList<int> globalcontext;
globalcontext << core->uniqueIDManager()->uniqueIdentifier(C_GLOBAL);
......@@ -360,30 +352,21 @@ bool CVSPlugin::initialize(const QStringList &arguments, QString *errorMessage)
m_submitRedoAction = new QAction(tr("&Redo"), this);
command = ami->registerAction(m_submitRedoAction, Core::Constants::REDO, cvscommitcontext);
connect(Core::ICore::instance(), SIGNAL(contextChanged(Core::IContext *)), this, SLOT(updateActions()));
return true;
}
void CVSPlugin::extensionsInitialized()
{
m_projectExplorer = ProjectExplorer::ProjectExplorerPlugin::instance();
if (m_projectExplorer) {
connect(m_projectExplorer,
SIGNAL(currentProjectChanged(ProjectExplorer::Project*)),
m_cvsPluginInstance, SLOT(updateActions()));
}
updateActions();
}
bool CVSPlugin::editorAboutToClose(Core::IEditor *iEditor)
bool CVSPlugin::submitEditorAboutToClose(VCSBase::VCSBaseSubmitEditor *submitEditor)
{
if (!iEditor || !isCommitEditorOpen() || qstrcmp(Constants::CVSCOMMITEDITOR, iEditor->kind()))
if (!isCommitEditorOpen())
return true;
Core::IFile *fileIFace = iEditor->file();
const CVSSubmitEditor *editor = qobject_cast<CVSSubmitEditor *>(iEditor);
Core::IFile *fileIFace = submitEditor->file();
const CVSSubmitEditor *editor = qobject_cast<CVSSubmitEditor *>(submitEditor);
if (!fileIFace || !editor)
return true;
......@@ -488,8 +471,11 @@ CVSSubmitEditor *CVSPlugin::openCVSSubmitEditor(const QString &fileName)
return submitEditor;
}
void CVSPlugin::updateActions()
void CVSPlugin::updateActions(VCSBase::VCSBasePlugin::ActionState as)
{
if (!VCSBase::VCSBasePlugin::enableMenuAction(as, m_menuAction))
return;
m_diffProjectAction->setEnabled(true);
m_commitAllAction->setEnabled(true);
m_statusAction->setEnabled(true);
......@@ -554,7 +540,7 @@ void CVSPlugin::revertCurrentFile()
const CVSResponse revertResponse = runCVS(args, files, cvsShortTimeOut, true);
if (revertResponse.result == CVSResponse::Ok) {
fcb.setModifiedReload(true);
m_versionControl->emitFilesChanged(files);
cvsVersionControl()->emitFilesChanged(files);
}
}
......@@ -738,7 +724,7 @@ void CVSPlugin::updateProject()
const CVSResponse response = runCVS(args, topLevels, cvsLongTimeOut, true);
if (response.result == CVSResponse::Ok)
foreach(const QString &topLevel, topLevels)
m_versionControl->emitRepositoryChanged(topLevel);
cvsVersionControl()->emitRepositoryChanged(topLevel);
}
}
......@@ -1193,6 +1179,11 @@ QString CVSPlugin::findTopLevelForDirectoryI(const QString &directory) const
return QString();
}
CVSControl *CVSPlugin::cvsVersionControl() const
{
return static_cast<CVSControl *>(versionControl());
}
}
}
Q_EXPORT_PLUGIN(CVS::Internal::CVSPlugin)
......@@ -33,8 +33,7 @@
#include "cvssettings.h"
#include "cvsutils.h"
#include <coreplugin/icorelistener.h>
#include <extensionsystem/iplugin.h>
#include <vcsbase/vcsbaseplugin.h>
QT_BEGIN_NAMESPACE
class QDir;
......@@ -55,6 +54,10 @@ namespace ProjectExplorer {
class ProjectExplorerPlugin;
}
namespace VCSBase {
class VCSBaseSubmitEditor;
}
namespace CVS {
namespace Internal {
......@@ -80,7 +83,7 @@ struct CVSResponse
* the diff editor has an additional property specifying the
* base directory for its interaction to work. */
class CVSPlugin : public ExtensionSystem::IPlugin
class CVSPlugin : public VCSBase::VCSBasePlugin
{
Q_OBJECT
......@@ -90,7 +93,6 @@ public:
virtual bool initialize(const QStringList &arguments, QString *error_message);
virtual void extensionsInitialized();
virtual bool editorAboutToClose(Core::IEditor *editor);
void cvsDiff(const QStringList &files, QString diffname = QString());
......@@ -108,7 +110,6 @@ public:
static CVSPlugin *cvsPluginInstance();
private slots:
void updateActions();
void addCurrentFile();
void deleteCurrentFile();
void revertCurrentFile();
......@@ -124,6 +125,10 @@ private slots:
void submitCurrentLog();
void diffFiles(const QStringList &);
protected:
virtual void updateActions(VCSBase::VCSBasePlugin::ActionState);
virtual bool submitEditorAboutToClose(VCSBase::VCSBaseSubmitEditor *submitEditor);
private:
bool isCommitEditorOpen() const;
QString currentFileName() const;
......@@ -152,9 +157,9 @@ private:
void startCommit(const QString &file);
bool commit(const QString &messageFile, const QStringList &subVersionFileList);
void cleanCommitMessageFile();
inline CVSControl *cvsVersionControl() const;
CVSSettings m_settings;
CVSControl *m_versionControl;
QString m_commitMessageFileName;
ProjectExplorer::ProjectExplorerPlugin *m_projectExplorer;
......@@ -175,32 +180,12 @@ private:
QAction *m_submitDiffAction;
QAction *m_submitUndoAction;
QAction *m_submitRedoAction;
QAction *m_menuAction;
bool m_submitActionTriggered;
static CVSPlugin *m_cvsPluginInstance;
};
// Just a proxy for CVSPlugin
class CoreListener : public Core::ICoreListener
{
Q_OBJECT
public:
CoreListener(CVSPlugin *plugin) : m_plugin(plugin) { }
// Start commit when submit editor closes
bool editorAboutToClose(Core::IEditor *editor) {
return m_plugin->editorAboutToClose(editor);
}
// TODO: how to handle that ???
bool coreAboutToClose() {
return true;
}
private:
CVSPlugin *m_plugin;
};
} // namespace CVS
} // namespace Internal
......
......@@ -32,7 +32,8 @@
#include <QtGui/QFileDialog>
#include <QtGui/QMessageBox>
using namespace Git::Internal;
namespace Git {
namespace Internal {
ChangeSelectionDialog::ChangeSelectionDialog(QWidget *parent)
: QDialog(parent)
......@@ -42,6 +43,21 @@ ChangeSelectionDialog::ChangeSelectionDialog(QWidget *parent)
setWindowTitle(tr("Select a Git commit"));
}
QString ChangeSelectionDialog::change() const
{
return m_ui.changeNumberEdit->text();
}
QString ChangeSelectionDialog::repository() const
{
return m_ui.repositoryEdit->text();
}
void ChangeSelectionDialog::setRepository(const QString &s)
{
m_ui.repositoryEdit->setText(s);
}
void ChangeSelectionDialog::selectWorkingDirectory()
{
static QString location = QString();
......@@ -67,3 +83,6 @@ void ChangeSelectionDialog::selectWorkingDirectory()
tr("Selected directory is not a Git repository"));
}
}
}
......@@ -37,21 +37,22 @@
namespace Git {
namespace Internal {
class GitPlugin;
class ChangeSelectionDialog : public QDialog
{
Q_OBJECT
public:
ChangeSelectionDialog(QWidget *parent = 0);
QString change() const;
QString repository() const;
void setRepository(const QString &s);
public slots:
void selectWorkingDirectory();
private:
friend class GitPlugin;
private:
Ui_ChangeSelectionDialog m_ui;
};
} // namespace Internal
......
......@@ -147,19 +147,6 @@ QString GitClient::findRepositoryForDirectory(const QString &dir)
return QString();
}
// Return source file or directory string depending on parameters
// ('git diff XX' -> 'XX' , 'git diff XX file' -> 'XX/file').
static QString source(const QString &workingDirectory, const QString &fileName)
{
if (fileName.isEmpty())
return workingDirectory;
QString rc = workingDirectory;
if (!rc.isEmpty() && !rc.endsWith(QDir::separator()))
rc += QDir::separator();
rc += fileName;
return rc;
}
/* Create an editor associated to VCS output of a source file/directory
* (using the file's codec). Makes use of a dynamic property to find an
* existing instance and to reuse it (in case, say, 'git diff foo' is
......@@ -253,8 +240,7 @@ void GitClient::diff(const QString &workingDirectory,
const QString kind = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_KIND);
const QString title = tr("Git Diff %1").arg(fileName);
const QString sourceFile = source(workingDirectory, fileName);
const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileName);
VCSBase::VCSBaseEditor *editor = createVCSEditor(kind, title, sourceFile, true, "originalFileName", sourceFile);
executeGit(workingDirectory, arguments, editor);
}
......@@ -267,10 +253,10 @@ void GitClient::status(const QString &workingDirectory)
executeGit(workingDirectory, statusArgs, 0, true);
}
void GitClient::log(const QString &workingDirectory, const QString &fileName)
void GitClient::log(const QString &workingDirectory, const QStringList &fileNames)
{
if (Git::Constants::debug)
qDebug() << "log" << workingDirectory << fileName;
qDebug() << "log" << workingDirectory << fileNames;
QStringList arguments;
arguments << QLatin1String("log") << QLatin1String(noColorOption);
......@@ -278,12 +264,14 @@ void GitClient::log(const QString &workingDirectory, const QString &fileName)
if (m_settings.logCount > 0)
arguments << QLatin1String("-n") << QString::number(m_settings.logCount);
if (!fileName.isEmpty())
arguments << fileName;
if (!fileNames.isEmpty())
arguments.append(fileNames);
const QString title = tr("Git Log %1").arg(fileName);
const QString msgArg = fileNames.empty() ? workingDirectory :
fileNames.join(QString(", "));
const QString title = tr("Git Log %1").arg(msgArg);
const QString kind = QLatin1String(Git::Constants::GIT_LOG_EDITOR_KIND);
const QString sourceFile = source(workingDirectory, fileName);
const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileNames);
VCSBase::VCSBaseEditor *editor = createVCSEditor(kind, title, sourceFile, false, "logFileName", sourceFile);
executeGit(workingDirectory, arguments, editor);
}
......@@ -313,7 +301,7 @@ void GitClient::blame(const QString &workingDirectory, const QString &fileName,
const QString kind = QLatin1String(Git::Constants::GIT_BLAME_EDITOR_KIND);
const QString title = tr("Git Blame %1").arg(fileName);