From 7ce3683143ead643e0dc2167f9f9283dd19eebbe Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <Friedemann.Kleint@nokia.com>
Date: Wed, 9 Dec 2009 12:41:10 +0100
Subject: [PATCH] CVS: Use new VCSBasePlugin-class.

Make the diff-base directory a property of VCSBaseEditor and use
everywhere.
---
 src/plugins/cvs/cvscontrol.cpp              |   9 +-
 src/plugins/cvs/cvseditor.cpp               |  26 +-
 src/plugins/cvs/cvseditor.h                 |   7 -
 src/plugins/cvs/cvsplugin.cpp               | 436 ++++++++------------
 src/plugins/cvs/cvsplugin.h                 |  43 +-
 src/plugins/git/gitclient.cpp               |   1 +
 src/plugins/git/giteditor.cpp               |  14 +-
 src/plugins/mercurial/mercurialclient.cpp   |   1 +
 src/plugins/mercurial/mercurialeditor.cpp   |   6 +-
 src/plugins/subversion/subversionplugin.cpp |   8 +
 src/plugins/vcsbase/vcsbaseeditor.cpp       |  26 +-
 src/plugins/vcsbase/vcsbaseeditor.h         |  11 +-
 12 files changed, 238 insertions(+), 350 deletions(-)

diff --git a/src/plugins/cvs/cvscontrol.cpp b/src/plugins/cvs/cvscontrol.cpp
index 01858d771ca..d4c3c5b4529 100644
--- a/src/plugins/cvs/cvscontrol.cpp
+++ b/src/plugins/cvs/cvscontrol.cpp
@@ -30,6 +30,8 @@
 #include "cvscontrol.h"
 #include "cvsplugin.h"
 
+#include <QtCore/QFileInfo>
+
 using namespace CVS;
 using namespace CVS::Internal;
 
@@ -66,12 +68,14 @@ bool CVSControl::vcsOpen(const QString & /* fileName */)
 
 bool CVSControl::vcsAdd(const QString &fileName)
 {
-    return m_plugin->vcsAdd(fileName);
+    const QFileInfo fi(fileName);
+    return m_plugin->vcsAdd(fi.absolutePath(), fi.fileName());
 }
 
 bool CVSControl::vcsDelete(const QString &fileName)
 {
-    return m_plugin->vcsDelete(fileName);
+    const QFileInfo fi(fileName);
+    return m_plugin->vcsDelete(fi.absolutePath(), fi.fileName());
 }
 
 bool CVSControl::managesDirectory(const QString &directory) const
@@ -93,4 +97,3 @@ void CVSControl::emitFilesChanged(const QStringList &l)
 {
     emit filesChanged(l);
 }
-
diff --git a/src/plugins/cvs/cvseditor.cpp b/src/plugins/cvs/cvseditor.cpp
index 03f81adb59f..8acc3e7fb30 100644
--- a/src/plugins/cvs/cvseditor.cpp
+++ b/src/plugins/cvs/cvseditor.cpp
@@ -142,35 +142,11 @@ QString CVSEditor::fileNameFromDiffSpecification(const QTextBlock &inBlock) cons
             const int tabIndex = diffFileName.indexOf(QLatin1Char('\t'));
             if (tabIndex != -1)
                 diffFileName.truncate(tabIndex);
-            // Add base dir
-            if (!m_diffBaseDir.isEmpty()) {
-                diffFileName.insert(0, QLatin1Char('/'));
-                diffFileName.insert(0, m_diffBaseDir);
-            }
-
-            if (CVS::Constants::debug)
-                qDebug() << "fileNameFromDiffSpecification" << m_diffBaseDir << diffFileName;
-            return diffFileName;
+            return findDiffFile(diffFileName);
         }
     }
     return QString();
 }
 
-QString CVSEditor::diffBaseDir() const
-{
-    return m_diffBaseDir;
-}
-
-void CVSEditor::setDiffBaseDir(const QString &d)
-{
-    m_diffBaseDir = d;
-}
-
-void CVSEditor::setDiffBaseDir(Core::IEditor *editor, const QString &db)
-{
-    if (CVSEditor *cvsEditor = qobject_cast<CVSEditor*>(editor->widget()))
-        cvsEditor->setDiffBaseDir(db);
-}
-
 }
 }
diff --git a/src/plugins/cvs/cvseditor.h b/src/plugins/cvs/cvseditor.h
index a9bb40eaf50..224ce44d797 100644
--- a/src/plugins/cvs/cvseditor.h
+++ b/src/plugins/cvs/cvseditor.h
@@ -45,13 +45,6 @@ public:
     explicit CVSEditor(const VCSBase::VCSBaseEditorParameters *type,
                             QWidget *parent);
 
-    // Diff mode requires a base directory since CVS commands
-    // are run with relative paths (see plugin).
-    QString diffBaseDir() const;
-    void setDiffBaseDir(const QString &d);
-
-    static void setDiffBaseDir(Core::IEditor *editor, const QString &db);
-
 private:
     virtual QSet<QString> annotationChanges() const;
     virtual QString changeUnderCursor(const QTextCursor &) const;
diff --git a/src/plugins/cvs/cvsplugin.cpp b/src/plugins/cvs/cvsplugin.cpp
index bfa406a39b0..2f2a70f6ef1 100644
--- a/src/plugins/cvs/cvsplugin.cpp
+++ b/src/plugins/cvs/cvsplugin.cpp
@@ -41,8 +41,6 @@
 #include <vcsbase/vcsbaseoutputwindow.h>
 #include <utils/synchronousprocess.h>
 #include <utils/parameteraction.h>
-#include <projectexplorer/session.h>
-#include <projectexplorer/project.h>
 
 #include <coreplugin/icore.h>
 #include <coreplugin/coreconstants.h>
@@ -53,7 +51,6 @@
 #include <coreplugin/actionmanager/actionmanager.h>
 #include <coreplugin/editormanager/editormanager.h>
 #include <coreplugin/vcsmanager.h>
-#include <projectexplorer/projectexplorer.h>
 #include <utils/stringutils.h>
 #include <utils/qtcassert.h>
 
@@ -152,7 +149,6 @@ CVSPlugin *CVSPlugin::m_cvsPluginInstance = 0;
 
 CVSPlugin::CVSPlugin() :
     VCSBase::VCSBasePlugin(QLatin1String(CVS::Constants::CVSCOMMITEDITOR_KIND)),
-    m_projectExplorer(0),
     m_addAction(0),
     m_deleteAction(0),
     m_revertAction(0),
@@ -162,7 +158,7 @@ CVSPlugin::CVSPlugin() :
     m_commitCurrentAction(0),
     m_filelogCurrentAction(0),
     m_annotateCurrentAction(0),
-    m_statusAction(0),
+    m_statusProjectAction(0),
     m_updateProjectAction(0),
     m_submitCurrentLogAction(0),
     m_submitDiffAction(0),
@@ -183,6 +179,7 @@ void CVSPlugin::cleanCommitMessageFile()
     if (!m_commitMessageFileName.isEmpty()) {
         QFile::remove(m_commitMessageFileName);
         m_commitMessageFileName.clear();
+        m_commitRepository.clear();
     }
 }
 bool CVSPlugin::isCommitEditorOpen() const
@@ -275,9 +272,10 @@ bool CVSPlugin::initialize(const QStringList & /*arguments */, QString *errorMes
 
     cvsMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR0, globalcontext));
 
-    m_diffProjectAction = new QAction(tr("Diff Project"), this);
+    m_diffProjectAction = new Utils::ParameterAction(tr("Diff Project"), tr("Diff Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
     command = ami->registerAction(m_diffProjectAction, CMD_ID_DIFF_PROJECT,
         globalcontext);
+    command->setAttribute(Core::Command::CA_UpdateText);
     connect(m_diffProjectAction, SIGNAL(triggered()), this, SLOT(diffProject()));
     cvsMenu->addAction(command);
 
@@ -325,14 +323,16 @@ bool CVSPlugin::initialize(const QStringList & /*arguments */, QString *errorMes
 
     cvsMenu->addAction(createSeparator(this, ami, CMD_ID_SEPARATOR3, globalcontext));
 
-    m_statusAction = new QAction(tr("Project Status"), this);
-    command = ami->registerAction(m_statusAction, CMD_ID_STATUS,
+    m_statusProjectAction = new Utils::ParameterAction(tr("Project Status"), tr("Status of Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
+    command = ami->registerAction(m_statusProjectAction, CMD_ID_STATUS,
         globalcontext);
-    connect(m_statusAction, SIGNAL(triggered()), this, SLOT(projectStatus()));
+    command->setAttribute(Core::Command::CA_UpdateText);
+    connect(m_statusProjectAction, SIGNAL(triggered()), this, SLOT(projectStatus()));
     cvsMenu->addAction(command);
 
-    m_updateProjectAction = new QAction(tr("Update Project"), this);
+    m_updateProjectAction = new Utils::ParameterAction(tr("Update Project"), tr("Update Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
     command = ami->registerAction(m_updateProjectAction, CMD_ID_UPDATE, globalcontext);
+    command->setAttribute(Core::Command::CA_UpdateText);
     connect(m_updateProjectAction, SIGNAL(triggered()), this, SLOT(updateProject()));
     cvsMenu->addAction(command);
 
@@ -357,7 +357,6 @@ bool CVSPlugin::initialize(const QStringList & /*arguments */, QString *errorMes
 
 void CVSPlugin::extensionsInitialized()
 {
-    m_projectExplorer = ProjectExplorer::ProjectExplorerPlugin::instance();
 }
 
 bool CVSPlugin::submitEditorAboutToClose(VCSBase::VCSBaseSubmitEditor *submitEditor)
@@ -410,27 +409,32 @@ bool CVSPlugin::submitEditorAboutToClose(VCSBase::VCSBaseSubmitEditor *submitEdi
     return closeEditor;
 }
 
-void CVSPlugin::diffFiles(const QStringList &files)
+void CVSPlugin::diffCommitFiles(const QStringList &files)
 {
-    cvsDiff(files);
+    cvsDiff(m_commitRepository, files);
 }
 
-void CVSPlugin::cvsDiff(const QStringList &files, QString diffname)
+static inline void setDiffBaseDirectory(Core::IEditor *editor, const QString &db)
 {
-    if (CVS::Constants::debug)
-        qDebug() << Q_FUNC_INFO << files << diffname;
-    const QString source = files.empty() ? QString() : files.front();
-    QTextCodec *codec = source.isEmpty() ? static_cast<QTextCodec *>(0) : VCSBase::VCSBaseEditor::getCodec(source);
+    if (VCSBase::VCSBaseEditor *ve = qobject_cast<VCSBase::VCSBaseEditor*>(editor->widget()))
+        ve->setDiffBaseDirectory(db);
+}
 
-    if (files.count() == 1 && diffname.isEmpty())
-        diffname = QFileInfo(files.front()).fileName();
+void CVSPlugin::cvsDiff(const QString &workingDir, const QStringList &files)
+{
+    if (CVS::Constants::debug)
+        qDebug() << Q_FUNC_INFO << files;
+    const QString source = VCSBase::VCSBaseEditor::getSource(workingDir, files);
+    QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(workingDir, files);
+    const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, files);
 
     QStringList args(QLatin1String("diff"));
     args << m_settings.cvsDiffOptions;
+    args.append(files);
 
     // CVS returns the diff exit code (1 if files differ), which is
     // undistinguishable from a "file not found" error, unfortunately.
-    const CVSResponse response = runCVS(args, files, cvsShortTimeOut, false, codec);
+    const CVSResponse response = runCVS(workingDir, args, cvsShortTimeOut, false, codec);
     switch (response.result) {
     case CVSResponse::NonNullExitCode:
     case CVSResponse::Ok:
@@ -446,18 +450,18 @@ void CVSPlugin::cvsDiff(const QStringList &files, QString diffname)
     // the common usage pattern of continuously changing and diffing a file
     if (files.count() == 1) {
         // Show in the same editor if diff has been executed before
-        if (Core::IEditor *editor = locateEditor("originalFileName", files.front())) {
+        if (Core::IEditor *editor = locateEditor("originalFileName", id)) {
             editor->createNew(output);
             Core::EditorManager::instance()->activateEditor(editor);
-            CVSEditor::setDiffBaseDir(editor, response.workingDirectory);
+            setDiffBaseDirectory(editor, workingDir);
             return;
         }
     }
-    const QString title = QString::fromLatin1("cvs diff %1").arg(diffname);
+    const QString title = QString::fromLatin1("cvs diff %1").arg(id);
     Core::IEditor *editor = showOutputInEditor(title, output, VCSBase::DiffOutput, source, codec);
     if (files.count() == 1)
-        editor->setProperty("originalFileName", files.front());
-    CVSEditor::setDiffBaseDir(editor, response.workingDirectory);
+        editor->setProperty("originalFileName", id);
+    setDiffBaseDirectory(editor, workingDir);
 }
 
 CVSSubmitEditor *CVSPlugin::openCVSSubmitEditor(const QString &fileName)
@@ -466,7 +470,7 @@ CVSSubmitEditor *CVSPlugin::openCVSSubmitEditor(const QString &fileName)
     CVSSubmitEditor *submitEditor = qobject_cast<CVSSubmitEditor*>(editor);
     QTC_ASSERT(submitEditor, /**/);
     submitEditor->registerActions(m_submitUndoAction, m_submitRedoAction, m_submitCurrentLogAction, m_submitDiffAction);
-    connect(submitEditor, SIGNAL(diffSelectedFiles(QStringList)), this, SLOT(diffFiles(QStringList)));
+    connect(submitEditor, SIGNAL(diffSelectedFiles(QStringList)), this, SLOT(diffCommitFiles(QStringList)));
 
     return submitEditor;
 }
@@ -476,45 +480,45 @@ 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);
-
-    const QString fileName = currentFileName();
-    const QString baseName = fileName.isEmpty() ? fileName : QFileInfo(fileName).fileName();
-
-    m_addAction->setParameter(baseName);
-    m_deleteAction->setParameter(baseName);
-    m_revertAction->setParameter(baseName);
-    m_diffCurrentAction->setParameter(baseName);
-    m_commitCurrentAction->setParameter(baseName);
-    m_filelogCurrentAction->setParameter(baseName);
-    m_annotateCurrentAction->setParameter(baseName);
+    const QString currentFileName = currentState().currentFileName();
+    m_addAction->setParameter(currentFileName);
+    m_deleteAction->setParameter(currentFileName);
+    m_revertAction->setParameter(currentFileName);
+    m_diffCurrentAction->setParameter(currentFileName);
+    m_commitCurrentAction->setParameter(currentFileName);
+    m_filelogCurrentAction->setParameter(currentFileName);
+    m_annotateCurrentAction->setParameter(currentFileName);
+
+    const QString currentProjectName = currentState().currentProjectName();
+    m_diffProjectAction->setParameter(currentProjectName);
+    m_statusProjectAction->setParameter(currentProjectName);
+    m_updateProjectAction->setParameter(currentProjectName);
+
+    m_commitAllAction->setEnabled(currentState().hasTopLevel());
 }
 
 void CVSPlugin::addCurrentFile()
 {
-    const QString file = currentFileName();
-    if (!file.isEmpty())
-        vcsAdd(file);
+    const VCSBase::VCSBasePluginState state = currentState();
+    QTC_ASSERT(state.hasFile(), return)
+    vcsAdd(state.currentFileTopLevel(), state.relativeCurrentFile());
 }
 
 void CVSPlugin::deleteCurrentFile()
 {
-    const QString file = currentFileName();
-    if (file.isEmpty())
-        return;
-    if (!Core::ICore::instance()->vcsManager()->showDeleteDialog(file))
-        QMessageBox::warning(0, QLatin1String("CVS remove"), tr("The file '%1' could not be deleted.").arg(file), QMessageBox::Ok);
+    const VCSBase::VCSBasePluginState state = currentState();
+    QTC_ASSERT(state.hasFile(), return)
+    if (!Core::ICore::instance()->vcsManager()->showDeleteDialog(state.currentFile()))
+        QMessageBox::warning(0, QLatin1String("CVS remove"), tr("The file '%1' could not be deleted.").arg(state.currentFile()), QMessageBox::Ok);
 }
 
 void CVSPlugin::revertCurrentFile()
 {
-    const QString file = currentFileName();
-    if (file.isEmpty())
-        return;
-
-    const CVSResponse diffResponse = runCVS(QStringList(QLatin1String("diff")), QStringList(file), cvsShortTimeOut, false);
+    const VCSBase::VCSBasePluginState state = currentState();
+    QTC_ASSERT(state.hasFile(), return)
+    QStringList args;
+    args << QLatin1String("diff") << state.relativeCurrentFile();
+    const CVSResponse diffResponse = runCVS(state.currentFileTopLevel(), args, cvsShortTimeOut, false);
     switch (diffResponse.result) {
     case CVSResponse::Ok:
         return; // Not modified, diff exit code 0
@@ -530,132 +534,81 @@ void CVSPlugin::revertCurrentFile()
                              QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
         return;
 
-    Core::FileChangeBlocker fcb(file);
+    Core::FileChangeBlocker fcb(state.currentFile());
 
     // revert
-    QStringList args(QLatin1String("update"));
-    args.push_back(QLatin1String("-C"));
-
-    const QStringList files = QStringList(file);
-    const CVSResponse revertResponse = runCVS(args, files, cvsShortTimeOut, true);
+    args.clear();
+    args << QLatin1String("update") << QLatin1String("-C") << state.relativeCurrentFile();
+    const CVSResponse revertResponse = runCVS(state.currentFileTopLevel(), args, cvsShortTimeOut, true);
     if (revertResponse.result == CVSResponse::Ok) {
         fcb.setModifiedReload(true);
-        cvsVersionControl()->emitFilesChanged(files);
-    }
-}
-
-// Get a unique set of toplevel directories for the current projects.
-// To be used for "diff all" or "commit all".
-QStringList CVSPlugin::currentProjectsTopLevels(QString *name) const
-{
-    typedef QList<ProjectExplorer::Project *> ProjectList;
-    ProjectList projects;
-    // Compile list of projects
-    if (ProjectExplorer::Project *currentProject = m_projectExplorer->currentProject()) {
-        projects.push_back(currentProject);
-    } else {
-        if (const ProjectExplorer::SessionManager *session = m_projectExplorer->session())
-            projects.append(session->projects());
-    }
-    // Get unique set of toplevels and concat project names
-    QStringList toplevels;
-    const QChar blank(QLatin1Char(' '));
-    foreach (const ProjectExplorer::Project *p,  projects) {
-        if (name) {
-            if (!name->isEmpty())
-                name->append(blank);
-            name->append(p->name());
-        }
-
-        const QString projectPath = QFileInfo(p->file()->fileName()).absolutePath();
-        const QString topLevel = findTopLevelForDirectory(projectPath);
-        if (!topLevel.isEmpty() && !toplevels.contains(topLevel))
-            toplevels.push_back(topLevel);
+        cvsVersionControl()->emitFilesChanged(QStringList(state.currentFile()));
     }
-    return toplevels;
 }
 
 void CVSPlugin::diffProject()
 {
-    QString diffName;
-    const QStringList topLevels = currentProjectsTopLevels(&diffName);
-    if (!topLevels.isEmpty())
-        cvsDiff(topLevels, diffName);
+    const VCSBase::VCSBasePluginState state = currentState();
+    QTC_ASSERT(state.hasProject(), return)
+    cvsDiff(state.currentProjectTopLevel(), state.relativeCurrentProject());
 }
 
 void CVSPlugin::diffCurrentFile()
 {
-    cvsDiff(QStringList(currentFileName()));
+    const VCSBase::VCSBasePluginState state = currentState();
+    QTC_ASSERT(state.hasFile(), return)
+    cvsDiff(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
 }
 
 void CVSPlugin::startCommitCurrentFile()
 {
-    const QString file = currentFileName();
-    if (!file.isEmpty())
-        startCommit(file);
+    const VCSBase::VCSBasePluginState state = currentState();
+    QTC_ASSERT(state.hasFile(), return)
+    startCommit(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
 }
 
 void CVSPlugin::startCommitAll()
 {
-    // Make sure we have only repository for commit
-    const QStringList files = currentProjectsTopLevels();
-    switch (files.size()) {
-    case 0:
-        break;
-    case 1:
-        startCommit(files.front());
-        break;
-    default: {
-        const QString msg = tr("The commit list spans several repositories (%1). Please commit them one by one.").
-            arg(files.join(QString(QLatin1Char(' '))));
-        QMessageBox::warning(0, QLatin1String("cvs commit"), msg, QMessageBox::Ok);
-    }
-        break;
-    }
+    const VCSBase::VCSBasePluginState state = currentState();
+    QTC_ASSERT(state.hasTopLevel(), return)
+    startCommit(state.topLevel());
 }
 
 /* Start commit of files of a single repository by displaying
  * template and files in a submit editor. On closing, the real
  * commit will start. */
-void CVSPlugin::startCommit(const QString &source)
+void CVSPlugin::startCommit(const QString &workingDir, const QStringList &files)
 {
-    if (source.isEmpty())
-        return;
     if (VCSBase::VCSBaseSubmitEditor::raiseSubmitEditor())
         return;
     if (isCommitEditorOpen()) {
         VCSBase::VCSBaseOutputWindow::instance()->appendWarning(tr("Another commit is currently being executed."));
         return;
     }
-    const QFileInfo sourceFi(source);
-    const QString sourceDir = sourceFi.isDir() ? source : sourceFi.absolutePath();
-    const QString topLevel = findTopLevelForDirectory(sourceDir);
-    if (topLevel.isEmpty()) {
-        VCSBase::VCSBaseOutputWindow::instance()->appendError(msgCannotFindTopLevel(source));
-        return;
-    }
+
     // We need the "Examining <subdir>" stderr output to tell
     // where we are, so, have stdout/stderr channels merged.
     QStringList args = QStringList(QLatin1String("status"));
-    if (sourceDir == topLevel) {
-        args.push_back(QString(QLatin1Char('.')));
-    } else {
-        args.push_back(QDir(topLevel).relativeFilePath(source));
-    }
-    const CVSResponse response = runCVS(topLevel, args, cvsShortTimeOut, false, 0, true);
+    const CVSResponse response = runCVS(workingDir, args, cvsShortTimeOut, false, 0, true);
     if (response.result != CVSResponse::Ok)
         return;
-    // Get list of added/modified/deleted files
-    // As we run cvs in the repository directory, we need complete
-    // the file names by the respective directory.
-    const StateList statusOutput = parseStatusOutput(topLevel, response.stdOut);
-    if (CVS::Constants::debug)
-        qDebug() << Q_FUNC_INFO << '\n' << source << "top" << topLevel;
-
+    // Get list of added/modified/deleted files and purge out undesired ones
+    // (do not run status with relative arguments as it will omit the directories)
+    StateList statusOutput = parseStatusOutput(QString(), response.stdOut);
+    if (!files.isEmpty()) {
+        for (StateList::iterator it = statusOutput.begin(); it != statusOutput.end() ; ) {
+            if (files.contains(it->second)) {
+                ++it;
+            } else {
+                it = statusOutput.erase(it);
+            }
+        }
+    }
     if (statusOutput.empty()) {
         VCSBase::VCSBaseOutputWindow::instance()->append(tr("There are no modified files."));
         return;
     }
+    m_commitRepository = workingDir;
 
     // Create a new submit change file containing the submit template
     QTemporaryFile changeTmpFile;
@@ -683,62 +636,73 @@ bool CVSPlugin::commit(const QString &messageFile,
         qDebug() << Q_FUNC_INFO << messageFile << fileList;
     QStringList args = QStringList(QLatin1String("commit"));
     args << QLatin1String("-F") << messageFile;
-    const CVSResponse response = runCVS(args, fileList, cvsLongTimeOut, true);
+    args.append(fileList);
+    const CVSResponse response = runCVS(m_commitRepository, args, cvsLongTimeOut, true);
     return response.result == CVSResponse::Ok ;
 }
 
 void CVSPlugin::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 CVSPlugin::filelog(const QString &file)
+void CVSPlugin::filelog(const QString &workingDir, const QStringList &files)
 {
-    QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(file);
+    QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(workingDir, files);
     // no need for temp file
-    const CVSResponse response = runCVS(QStringList(QLatin1String("log")), QStringList(file), cvsShortTimeOut, false, codec);
+    const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, files);
+    const QString source = VCSBase::VCSBaseEditor::getSource(workingDir, files);
+    QStringList args;
+    args << QLatin1String("log");
+    args.append(files);
+    const CVSResponse response = runCVS(workingDir, args, cvsShortTimeOut, false, codec);
     if (response.result != CVSResponse::Ok)
         return;
 
     // Re-use an existing view if possible to support
     // the common usage pattern of continuously changing and diffing a file
 
-    if (Core::IEditor *editor = locateEditor("logFileName", file)) {
+    if (Core::IEditor *editor = locateEditor("logFileName", id)) {
         editor->createNew(response.stdOut);
         Core::EditorManager::instance()->activateEditor(editor);
     } else {
-        const QString title = QString::fromLatin1("cvs log %1").arg(QFileInfo(file).fileName());
-        Core::IEditor *newEditor = showOutputInEditor(title, response.stdOut, VCSBase::LogOutput, file, codec);
-        newEditor->setProperty("logFileName", file);
+        const QString title = QString::fromLatin1("cvs log %1").arg(id);
+        Core::IEditor *newEditor = showOutputInEditor(title, response.stdOut, VCSBase::LogOutput, source, codec);
+        newEditor->setProperty("logFileName", id);
     }
 }
 
 void CVSPlugin::updateProject()
 {
-    const QStringList topLevels = currentProjectsTopLevels();
-    if (!topLevels.empty()) {
-        QStringList args(QLatin1String("update"));
-        args.push_back(QLatin1String("-dR"));
-        const CVSResponse response = runCVS(args, topLevels, cvsLongTimeOut, true);
-        if (response.result == CVSResponse::Ok)
-            foreach(const QString &topLevel, topLevels)
-                cvsVersionControl()->emitRepositoryChanged(topLevel);
-    }
+    const VCSBase::VCSBasePluginState state = currentState();
+    QTC_ASSERT(state.hasProject(), return)
+
+   QStringList args(QLatin1String("update"));
+   args.push_back(QLatin1String("-dR"));
+   args.append(state.relativeCurrentProject());
+   const CVSResponse response = runCVS(state.currentProjectTopLevel(), args, cvsLongTimeOut, true);
+   if (response.result == CVSResponse::Ok)
+       cvsVersionControl()->emitRepositoryChanged(state.currentProjectTopLevel());
 }
 
 void CVSPlugin::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 CVSPlugin::annotate(const QString &file)
+void CVSPlugin::annotate(const QString &workingDir, const QString &file)
 {
-    QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(file);
-    const CVSResponse response = runCVS(QStringList(QLatin1String("annotate")), QStringList(file), cvsShortTimeOut, false, codec);
+    const QStringList files(file);
+    QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(workingDir, files);
+    const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, files);
+    const QString source = VCSBase::VCSBaseEditor::getSource(workingDir, file);
+    QStringList args;
+    args << QLatin1String("annotate") << file;
+    const CVSResponse response = runCVS(workingDir, args, cvsShortTimeOut, false, codec);
     if (response.result != CVSResponse::Ok)
         return;
 
@@ -746,30 +710,27 @@ void CVSPlugin::annotate(const QString &file)
     // the common usage pattern of continuously changing and diffing a file
     const int lineNumber = VCSBase::VCSBaseEditor::lineNumberOfCurrentEditor(file);
 
-    if (Core::IEditor *editor = locateEditor("annotateFileName", file)) {
+    if (Core::IEditor *editor = locateEditor("annotateFileName", id)) {
         editor->createNew(response.stdOut);
         VCSBase::VCSBaseEditor::gotoLineOfEditor(editor, lineNumber);
         Core::EditorManager::instance()->activateEditor(editor);
     } else {
-        const QString title = QString::fromLatin1("cvs annotate %1").arg(QFileInfo(file).fileName());
-        Core::IEditor *newEditor = showOutputInEditor(title, response.stdOut, VCSBase::AnnotateOutput, file, codec);
-        newEditor->setProperty("annotateFileName", file);
+        const QString title = QString::fromLatin1("cvs annotate %1").arg(id);
+        Core::IEditor *newEditor = showOutputInEditor(title, response.stdOut, VCSBase::AnnotateOutput, source, codec);
+        newEditor->setProperty("annotateFileName", id);
         VCSBase::VCSBaseEditor::gotoLineOfEditor(newEditor, lineNumber);
     }
 }
 
 void CVSPlugin::projectStatus()
 {
-    if (!m_projectExplorer)
-        return;
-
-    const QStringList topLevels = currentProjectsTopLevels();
-    if (topLevels.empty())
-        return;
-
-    const CVSResponse response = runCVS(QStringList(QLatin1String("status")), topLevels, cvsShortTimeOut, false);
+    const VCSBase::VCSBasePluginState state = currentState();
+    QTC_ASSERT(state.hasProject(), return)
+    QStringList args;
+    args << QLatin1String("status") << state.relativeCurrentProject();
+    const CVSResponse response = runCVS(state.currentProjectTopLevel(), args, cvsShortTimeOut, false);
     if (response.result == CVSResponse::Ok)
-        showOutputInEditor(tr("Project status"), response.stdOut, VCSBase::RegularCommandOutput, topLevels.front(), 0);
+        showOutputInEditor(tr("Project status"), response.stdOut, VCSBase::RegularCommandOutput, state.currentProjectTopLevel(), 0);
 }
 
 // Decrement version number "1.2" -> "1.1"
@@ -797,6 +758,18 @@ void CVSPlugin::slotDescribe(const QString &source, const QString &changeNr)
 
 bool CVSPlugin::describe(const QString &file, const QString &changeNr, QString *errorMessage)
 {
+    const QString toplevel = findTopLevelForDirectory(QFileInfo(file).absolutePath());
+    if (toplevel.isEmpty()) {
+        *errorMessage = msgCannotFindTopLevel(file);
+        return false;
+    }
+    return describe(toplevel, QDir(toplevel).relativeFilePath(file), changeNr, errorMessage);
+}
+
+bool CVSPlugin::describe(const QString &toplevel, const QString &file, const
+                         QString &changeNr, QString *errorMessage)
+{
+
     // In CVS, revisions of files are normally unrelated, there is
     // no global revision/change number. The only thing that groups
     // a commit is the "commit-id" (as shown in the log).
@@ -805,25 +778,20 @@ bool CVSPlugin::describe(const QString &file, const QString &changeNr, QString *
     // if desired.
     if (CVS::Constants::debug)
         qDebug() << Q_FUNC_INFO << file << changeNr;
-    const QString toplevel = findTopLevelForDirectory(QFileInfo(file).absolutePath());
-    if (toplevel.isEmpty()) {
-        *errorMessage = msgCannotFindTopLevel(file);
-        return false;
-    }
     // Number must be > 1
     if (isFirstRevision(changeNr)) {
         *errorMessage = tr("The initial revision %1 cannot be described.").arg(changeNr);
         return false;
     }
     // Run log to obtain commit id and details
-    QStringList args(QLatin1String("log"));
-    args.push_back(QLatin1String("-r") + changeNr);
-    const CVSResponse logResponse = runCVS(args, QStringList(file), cvsShortTimeOut, false);
+    QStringList args;
+    args << QLatin1String("log") << (QLatin1String("-r") + changeNr) << file;
+    const CVSResponse logResponse = runCVS(toplevel, args, cvsShortTimeOut, false);
     if (logResponse.result != CVSResponse::Ok) {
         *errorMessage = logResponse.message;
         return false;
     }
-    const QList<CVS_LogEntry> fileLog = parseLogEntries(logResponse.stdOut, logResponse.workingDirectory);
+    const QList<CVS_LogEntry> fileLog = parseLogEntries(logResponse.stdOut);
     if (fileLog.empty() || fileLog.front().revisions.empty()) {
         *errorMessage = msgLogParsingFailed();
         return false;
@@ -838,13 +806,14 @@ bool CVSPlugin::describe(const QString &file, const QString &changeNr, QString *
         const QString nextDayS = date.addDays(1).toString(Qt::ISODate);
         args.clear();
         args << QLatin1String("log") << QLatin1String("-d") << (dateS  + QLatin1Char('<') + nextDayS);
-        const CVSResponse repoLogResponse = runCVS(args, QStringList(toplevel), cvsLongTimeOut, false);
+
+        const CVSResponse repoLogResponse = runCVS(toplevel, args, cvsLongTimeOut, false);
         if (repoLogResponse.result != CVSResponse::Ok) {
             *errorMessage = repoLogResponse.message;
             return false;
         }
         // Describe all files found, pass on dir to obtain correct absolute paths.
-        const QList<CVS_LogEntry> repoEntries = parseLogEntries(repoLogResponse.stdOut, QFileInfo(toplevel).absolutePath(), commitId);
+        const QList<CVS_LogEntry> repoEntries = parseLogEntries(repoLogResponse.stdOut, QString(), commitId);
         if (repoEntries.empty()) {
             *errorMessage = tr("Could not find commits of id '%1' on %2.").arg(commitId, dateS);
             return false;
@@ -859,20 +828,18 @@ bool CVSPlugin::describe(const QString &file, const QString &changeNr, QString *
 
 // Describe a set of files and revisions by
 // concatenating log and diffs to previous revisions
-bool CVSPlugin::describe(const QString &repositoryPath, QList<CVS_LogEntry> entries,
+bool CVSPlugin::describe(const QString &repositoryPath,
+                         QList<CVS_LogEntry> entries,
                          QString *errorMessage)
 {
     // Collect logs
     QString output;
-    const QDir repository(repositoryPath);
     QTextCodec *codec = 0;
     const QList<CVS_LogEntry>::iterator lend = entries.end();
     for (QList<CVS_LogEntry>::iterator it = entries.begin(); it != lend; ++it) {
         // Before fiddling file names, try to find codec
         if (!codec)
-            codec = VCSBase::VCSBaseEditor::getCodec(it->file);
-        // Make the files relative to the repository directory.
-        it->file = repository.relativeFilePath(it->file);
+            codec = VCSBase::VCSBaseEditor::getCodec(repositoryPath, QStringList(it->file));
         // Run log
         QStringList args(QLatin1String("log"));
         args << (QLatin1String("-r") + it->revisions.front().revision) << it->file;
@@ -915,12 +882,12 @@ bool CVSPlugin::describe(const QString &repositoryPath, QList<CVS_LogEntry> entr
     if (Core::IEditor *editor = locateEditor("describeChange", commitId)) {
         editor->createNew(output);
         Core::EditorManager::instance()->activateEditor(editor);
-        CVSEditor::setDiffBaseDir(editor, repositoryPath);
+        setDiffBaseDirectory(editor, repositoryPath);
     } else {
         const QString title = QString::fromLatin1("cvs describe %1").arg(commitId);
         Core::IEditor *newEditor = showOutputInEditor(title, output, VCSBase::DiffOutput, entries.front().file, codec);
         newEditor->setProperty("describeChange", commitId);
-        CVSEditor::setDiffBaseDir(newEditor, repositoryPath);
+        setDiffBaseDirectory(editor, repositoryPath);
     }
     return true;
 }
@@ -932,56 +899,11 @@ void CVSPlugin::submitCurrentLog()
         << Core::EditorManager::instance()->currentEditor());
 }
 
-QString CVSPlugin::currentFileName() const
-{
-    const QString fileName = Core::ICore::instance()->fileManager()->currentFile();
-    if (!fileName.isEmpty()) {
-        const QFileInfo fi(fileName);
-        if (fi.exists())
-            return fi.canonicalFilePath();
-    }
-    return QString();
-}
-
 static inline QString processStdErr(QProcess &proc)
 {
     return QString::fromLocal8Bit(proc.readAllStandardError()).remove(QLatin1Char('\r'));
 }
 
-/* Tortoise CVS does not allow for absolute path names
- * (which it claims to be CVS standard behaviour).
- * So, try to figure out the common root of the file arguments,
- * remove it from the files and return it as working directory for
- * the process. Note that it is principle possible to have
- * projects with differing repositories open in a session,
- * so, trying to find a common repository is not an option.
- * Usually, there is only one file argument, which is not
- * problematic; it is just split using QFileInfo. */
-
-static inline QString fixFileArgs(QStringList *files)
-{
-    switch (files->size()) {
-    case 0:
-        return QString();
-    case 1: { // Easy, just one
-            const QFileInfo fi(files->at(0));
-            (*files)[0] = fi.fileName();
-            return fi.absolutePath();
-        }
-    default:
-        break;
-    }
-    // Find common directory part: "C:\foo\bar" -> "C:\foo"
-    const QString common = Utils::commonPath(*files);
-    // remove up until slash from the files
-    const int commonLength = common.size() + 1;
-    const QStringList::iterator end = files->end();
-    for (QStringList::iterator it = files->begin(); it != end; ++it) {
-        it->remove(0, commonLength);
-    }
-    return common;
-}
-
 // Format log entry for command
 static inline QString msgExecutionLogEntry(const QString &workingDir, const QString &executable, const QStringList &arguments)
 {
@@ -992,19 +914,6 @@ static inline QString msgExecutionLogEntry(const QString &workingDir, const QStr
     return CVSPlugin::tr("Executing in %1: %2 %3\n").arg(workingDir, executable, args);
 }
 
-// Figure out a working directory for the process,
-// fix the file arguments accordingly and run CVS.
-CVSResponse CVSPlugin::runCVS(const QStringList &arguments,
-                              QStringList files,
-                              int timeOut,
-                              bool showStdOutInOutputWindow,
-                              QTextCodec *outputCodec,
-                              bool mergeStderr)
-{
-    const QString workingDirectory = fixFileArgs(&files);
-    return runCVS( workingDirectory, arguments + files, timeOut, showStdOutInOutputWindow, outputCodec, mergeStderr);
-}
-
 // Run CVS. At this point, file arguments must be relative to
 // the working directory (see above).
 CVSResponse CVSPlugin::runCVS(const QString &workingDirectory,
@@ -1021,10 +930,9 @@ CVSResponse CVSPlugin::runCVS(const QString &workingDirectory,
         return response;
     }
     // Fix files and compile complete arguments
-    response.workingDirectory = workingDirectory;
     const QStringList allArgs = m_settings.addOptions(arguments);
 
-    const QString outputText = msgExecutionLogEntry(response.workingDirectory, executable, allArgs);
+    const QString outputText = msgExecutionLogEntry(workingDirectory, executable, allArgs);
     VCSBase::VCSBaseOutputWindow::instance()->appendCommand(outputText);
 
     if (CVS::Constants::debug)
@@ -1032,8 +940,8 @@ CVSResponse CVSPlugin::runCVS(const QString &workingDirectory,
 
     // Run, connect stderr to the output window
     Utils::SynchronousProcess process;
-    if (!response.workingDirectory.isEmpty())
-        process.setWorkingDirectory(response.workingDirectory);
+    if (!workingDirectory.isEmpty())
+        process.setWorkingDirectory(workingDirectory);
 
     if (mergeStderr)
         process.setProcessChannelMode(QProcess::MergedChannels);
@@ -1124,17 +1032,19 @@ CVSPlugin *CVSPlugin::cvsPluginInstance()
     return m_cvsPluginInstance;
 }
 
-bool CVSPlugin::vcsAdd(const QString &rawFileName)
+bool CVSPlugin::vcsAdd(const QString &workingDir, const QString &rawFileName)
 {
-    const CVSResponse response = runCVS(QStringList(QLatin1String("add")), QStringList(rawFileName), cvsShortTimeOut, true);
+    QStringList args;
+    args << QLatin1String("add") << rawFileName;
+    const CVSResponse response = runCVS(workingDir, args, cvsShortTimeOut, true);
     return response.result == CVSResponse::Ok;
 }
 
-bool CVSPlugin::vcsDelete(const QString &rawFileName)
+bool CVSPlugin::vcsDelete(const QString &workingDir, const QString &rawFileName)
 {
-    QStringList args(QLatin1String("remove"));
-    args << QLatin1String("-f");
-    const CVSResponse response = runCVS(args, QStringList(rawFileName), cvsShortTimeOut, true);
+    QStringList args;
+    args << QLatin1String("remove") << QLatin1String("-f") << rawFileName;
+    const CVSResponse response = runCVS(workingDir, args, cvsShortTimeOut, true);
     return response.result == CVSResponse::Ok;
 }
 
diff --git a/src/plugins/cvs/cvsplugin.h b/src/plugins/cvs/cvsplugin.h
index 268c4848afc..25f544dd42b 100644
--- a/src/plugins/cvs/cvsplugin.h
+++ b/src/plugins/cvs/cvsplugin.h
@@ -50,10 +50,6 @@ namespace Utils {
     class ParameterAction;
 }
 
-namespace ProjectExplorer {
-    class ProjectExplorerPlugin;
-}
-
 namespace VCSBase {
     class VCSBaseSubmitEditor;
 }
@@ -73,16 +69,8 @@ struct CVSResponse
     QString stdOut;
     QString stdErr;
     QString message;
-    QString workingDirectory;
 };
 
-/* This plugin differs from the other VCS plugins in that it
- * runs CVS commands from a working directory using relative
- * path specifications, which is a requirement imposed by
- * Tortoise CVS. This has to be taken into account; for example,
- * the diff editor has an additional property specifying the
- * base directory for its interaction to work. */
-
 class CVSPlugin : public VCSBase::VCSBasePlugin
 {
     Q_OBJECT
@@ -94,7 +82,7 @@ public:
     virtual bool initialize(const QStringList &arguments, QString *error_message);
     virtual void extensionsInitialized();
 
-    void cvsDiff(const QStringList &files, QString diffname = QString());
+    void cvsDiff(const QString &workingDir, const QStringList &files);
 
     CVSSubmitEditor *openCVSSubmitEditor(const QString &fileName);
 
@@ -102,8 +90,8 @@ public:
     void setSettings(const CVSSettings &s);
 
     // IVersionControl
-    bool vcsAdd(const QString &fileName);
-    bool vcsDelete(const QString &fileName);
+    bool vcsAdd(const QString &workingDir, const QString &fileName);
+    bool vcsDelete(const QString &workingDir, const QString &fileName);
     bool managesDirectory(const QString &directory) const;
     QString findTopLevelForDirectory(const QString &directory) const;
 
@@ -123,7 +111,7 @@ private slots:
     void slotDescribe(const QString &source, const QString &changeNr);
     void updateProject();
     void submitCurrentLog();
-    void diffFiles(const QStringList &);
+    void diffCommitFiles(const QStringList &);
 
 protected:
     virtual void updateActions(VCSBase::VCSBasePlugin::ActionState);
@@ -131,15 +119,9 @@ protected:
 
 private:
     bool isCommitEditorOpen() const;
-    QString currentFileName() const;
     Core::IEditor * showOutputInEditor(const QString& title, const QString &output,
                                        int editorType, const QString &source,
                                        QTextCodec *codec);
-    CVSResponse runCVS(const QStringList &arguments,
-                       QStringList fileArguments,
-                       int timeOut,
-                       bool showStdOutInOutputWindow, QTextCodec *outputCodec = 0,
-                       bool mergeStderr = false);
 
     CVSResponse runCVS(const QString &workingDirectory,
                        const QStringList &arguments,
@@ -147,34 +129,33 @@ private:
                        bool showStdOutInOutputWindow, QTextCodec *outputCodec = 0,
                        bool mergeStderr = false);
 
-    void annotate(const QString &file);
+    void annotate(const QString &workingDir, const QString &file);
     bool describe(const QString &source, const QString &changeNr, QString *errorMessage);
+    bool describe(const QString &toplevel, const QString &source, const QString &changeNr, QString *errorMessage);
     bool describe(const QString &repository, QList<CVS_LogEntry> entries, QString *errorMessage);
-    void filelog(const QString &file);
+    void filelog(const QString &workingDir, const QStringList &files = QStringList());
     bool managesDirectory(const QDir &directory) const;
     QString findTopLevelForDirectoryI(const QString &directory) const;
-    QStringList currentProjectsTopLevels(QString *name = 0) const;
-    void startCommit(const QString &file);
+    void startCommit(const QString &workingDir, const QStringList &files = QStringList());
     bool commit(const QString &messageFile, const QStringList &subVersionFileList);
     void cleanCommitMessageFile();
     inline CVSControl *cvsVersionControl() const;
 
     CVSSettings m_settings;
     QString m_commitMessageFileName;
-
-    ProjectExplorer::ProjectExplorerPlugin *m_projectExplorer;
+    QString m_commitRepository;
 
     Utils::ParameterAction *m_addAction;
     Utils::ParameterAction *m_deleteAction;
     Utils::ParameterAction *m_revertAction;
-    QAction *m_diffProjectAction;
+    Utils::ParameterAction *m_diffProjectAction;
     Utils::ParameterAction *m_diffCurrentAction;
     QAction *m_commitAllAction;
     Utils::ParameterAction *m_commitCurrentAction;
     Utils::ParameterAction *m_filelogCurrentAction;
     Utils::ParameterAction *m_annotateCurrentAction;
-    QAction *m_statusAction;
-    QAction *m_updateProjectAction;
+    Utils::ParameterAction *m_statusProjectAction;
+    Utils::ParameterAction *m_updateProjectAction;
 
     QAction *m_submitCurrentLogAction;
     QAction *m_submitDiffAction;
diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp
index a7fda8a1abc..e5bbd87a37e 100644
--- a/src/plugins/git/gitclient.cpp
+++ b/src/plugins/git/gitclient.cpp
@@ -196,6 +196,7 @@ void GitClient::diff(const QString &workingDirectory,
     const QString title = tr("Git Diff");
 
     VCSBase::VCSBaseEditor *editor = createVCSEditor(kind, title, workingDirectory, true, "originalFileName", workingDirectory);
+    editor->setDiffBaseDirectory(workingDirectory);
 
     // Create a batch of 2 commands to be run after each other in case
     // we have a mixture of staged/unstaged files as is the case
diff --git a/src/plugins/git/giteditor.cpp b/src/plugins/git/giteditor.cpp
index fe03336035e..6dd67f86d9e 100644
--- a/src/plugins/git/giteditor.cpp
+++ b/src/plugins/git/giteditor.cpp
@@ -30,9 +30,9 @@
 #include "giteditor.h"
 
 #include "annotationhighlighter.h"
-#include "gitclient.h"
 #include "gitconstants.h"
 #include "gitplugin.h"
+#include "gitsettings.h"
 #include <QtCore/QTextCodec>
 
 #include <coreplugin/editormanager/editormanager.h>
@@ -122,21 +122,14 @@ VCSBase::BaseAnnotationHighlighter *GitEditor::createAnnotationHighlighter(const
 
 QString GitEditor::fileNameFromDiffSpecification(const QTextBlock &inBlock) const
 {
-    QString errorMessage;
-    // Check for "+++ b/src/plugins/git/giteditor.cpp" (blame and diff)
+        // Check for "+++ b/src/plugins/git/giteditor.cpp" (blame and diff)
     // Go back chunks.
     const QString newFileIndicator = QLatin1String("+++ b/");
     for (QTextBlock  block = inBlock; block.isValid(); block = block.previous()) {
         QString diffFileName = block.text();
         if (diffFileName.startsWith(newFileIndicator)) {
             diffFileName.remove(0, newFileIndicator.size());
-            const QString fileOrDir = source();
-            const QString repo = QFileInfo(fileOrDir).isDir() ?
-                GitClient::findRepositoryForDirectory(fileOrDir) : GitClient::findRepositoryForFile(fileOrDir);
-            const QString absPath = QDir(repo).absoluteFilePath(diffFileName);
-            if (Git::Constants::debug)
-                qDebug() << "fileNameFromDiffSpecification" << repo << diffFileName << absPath;
-            return absPath;
+            return findDiffFile(diffFileName, GitPlugin::instance()->versionControl());
         }
     }
     return QString();
@@ -195,3 +188,4 @@ void GitEditor::commandFinishedGotoLine(bool ok, const QVariant &v)
 
 } // namespace Internal
 } // namespace Git
+
diff --git a/src/plugins/mercurial/mercurialclient.cpp b/src/plugins/mercurial/mercurialclient.cpp
index c6effd96a38..fa5321630e5 100644
--- a/src/plugins/mercurial/mercurialclient.cpp
+++ b/src/plugins/mercurial/mercurialclient.cpp
@@ -190,6 +190,7 @@ void MercurialClient::diff(const QString &workingDir, const QStringList &files)
     const QString source = VCSBase::VCSBaseEditor::getSource(workingDir, files);
     VCSBase::VCSBaseEditor *editor = createVCSEditor(kind, title, source, true,
                                                      "diff", id);
+    editor->setDiffBaseDirectory(workingDir);
 
     QSharedPointer<HgTask> job(new HgTask(workingDir, args, editor));
     enqueueJob(job);
diff --git a/src/plugins/mercurial/mercurialeditor.cpp b/src/plugins/mercurial/mercurialeditor.cpp
index ba6989db515..9315c3dad23 100644
--- a/src/plugins/mercurial/mercurialeditor.cpp
+++ b/src/plugins/mercurial/mercurialeditor.cpp
@@ -30,7 +30,7 @@
 #include "mercurialeditor.h"
 #include "annotationhighlighter.h"
 #include "constants.h"
-#include "mercurialclient.h"
+#include "mercurialplugin.h"
 
 #include <coreplugin/editormanager/editormanager.h>
 #include <vcsbase/diffhighlighter.h>
@@ -103,10 +103,8 @@ QString MercurialEditor::fileNameFromDiffSpecification(const QTextBlock &diffFil
         QTextFragment fragment = iterator.fragment();
         if(fragment.isValid()) {
             if (fragment.text().startsWith(filechangeId)) {
-                const QFileInfo sourceFile(source());
-                const QDir repository(MercurialClient::findTopLevelForFile(sourceFile));
                 const QString filename = fragment.text().remove(0, filechangeId.size());
-                return repository.absoluteFilePath(filename);
+                return findDiffFile(filename, MercurialPlugin::instance()->versionControl());
             }
         }
     }
diff --git a/src/plugins/subversion/subversionplugin.cpp b/src/plugins/subversion/subversionplugin.cpp
index eece5a9e85a..cc6978cf027 100644
--- a/src/plugins/subversion/subversionplugin.cpp
+++ b/src/plugins/subversion/subversionplugin.cpp
@@ -451,6 +451,12 @@ void SubversionPlugin::diffCommitFiles(const QStringList &files)
     svnDiff(m_commitRepository, files);
 }
 
+static inline void setDiffBaseDirectory(Core::IEditor *editor, const QString &db)
+{
+    if (VCSBase::VCSBaseEditor *ve = qobject_cast<VCSBase::VCSBaseEditor*>(editor->widget()))
+        ve->setDiffBaseDirectory(db);
+}
+
 void SubversionPlugin::svnDiff(const QString &workingDir, const QStringList &files, QString diffname)
 {
     if (Subversion::Constants::debug)
@@ -475,11 +481,13 @@ void SubversionPlugin::svnDiff(const QString &workingDir, const QStringList &fil
         if (Core::IEditor *editor = locateEditor("originalFileName", files.front())) {
             editor->createNew(response.stdOut);
             Core::EditorManager::instance()->activateEditor(editor);
+            setDiffBaseDirectory(editor, workingDir);
             return;
         }
     }
     const QString title = QString::fromLatin1("svn diff %1").arg(diffname);
     Core::IEditor *editor = showOutputInEditor(title, response.stdOut, VCSBase::DiffOutput, source, codec);
+    setDiffBaseDirectory(editor, workingDir);
     if (files.count() == 1)
         editor->setProperty("originalFileName", files.front());
 }
diff --git a/src/plugins/vcsbase/vcsbaseeditor.cpp b/src/plugins/vcsbase/vcsbaseeditor.cpp
index a6c39e5de75..70c6ab67bf9 100644
--- a/src/plugins/vcsbase/vcsbaseeditor.cpp
+++ b/src/plugins/vcsbase/vcsbaseeditor.cpp
@@ -147,6 +147,7 @@ struct VCSBaseEditorPrivate
     QAction *m_describeAction;
     QString m_currentChange;
     QString m_source;
+    QString m_diffBaseDirectory;
 
     QRegExp m_diffFilePattern;
     QList<int> m_diffSections; // line number where this section starts
@@ -210,6 +211,16 @@ void VCSBaseEditor::setSource(const  QString &source)
     d->m_source = source;
 }
 
+QString VCSBaseEditor::diffBaseDirectory() const
+{
+    return d->m_diffBaseDirectory;
+}
+
+void VCSBaseEditor::setDiffBaseDirectory(const QString &bd)
+{
+    d->m_diffBaseDirectory = bd;
+}
+
 QTextCodec *VCSBaseEditor::codec() const
 {
     return baseTextDocument()->codec();
@@ -678,18 +689,25 @@ QString VCSBaseEditor::getTitleId(const QString &workingDirectory, const QString
 // Find the complete file from a diff relative specification.
 QString VCSBaseEditor::findDiffFile(const QString &f, Core::IVersionControl *control /* = 0 */) const
 {
-    // Try the file.
+    // Try the file itself, expand to absolute.
     const QFileInfo in(f);
     if (in.isAbsolute())
         return in.isFile() ? f : QString();
     if (in.isFile())
         return in.absoluteFilePath();
-    // Try in source directory
+    // 1) Try base dir
+    const QChar slash = QLatin1Char('/');
+    if (!d->m_diffBaseDirectory.isEmpty()) {
+        const QFileInfo baseFileInfo(d->m_diffBaseDirectory + slash + f);
+        if (baseFileInfo.isFile())
+            return baseFileInfo.absoluteFilePath();
+    }
+    // 2) Try in source (which can be file or directory)
     if (source().isEmpty())
         return QString();
     const QFileInfo sourceInfo(source());
     const QString sourceDir = sourceInfo.isDir() ? sourceInfo.absoluteFilePath() : sourceInfo.absolutePath();
-    const QFileInfo sourceFileInfo(sourceDir + QLatin1Char('/') + f);
+    const QFileInfo sourceFileInfo(sourceDir + slash + f);
     if (sourceFileInfo.isFile())
         return sourceFileInfo.absoluteFilePath();
     // Try to locate via repository.
@@ -698,7 +716,7 @@ QString VCSBaseEditor::findDiffFile(const QString &f, Core::IVersionControl *con
     const QString topLevel = control->findTopLevelForDirectory(sourceDir);
     if (topLevel.isEmpty())
         return QString();
-    const QFileInfo topLevelFileInfo(topLevel + QLatin1Char('/') + f);
+    const QFileInfo topLevelFileInfo(topLevel + slash + f);
     if (topLevelFileInfo.isFile())
         return topLevelFileInfo.absoluteFilePath();
     return QString();
diff --git a/src/plugins/vcsbase/vcsbaseeditor.h b/src/plugins/vcsbase/vcsbaseeditor.h
index 732b40c57d7..0969694b308 100644
--- a/src/plugins/vcsbase/vcsbaseeditor.h
+++ b/src/plugins/vcsbase/vcsbaseeditor.h
@@ -87,6 +87,7 @@ struct VCSBASE_EXPORT VCSBaseEditorParameters {
 class VCSBASE_EXPORT VCSBaseEditor : public TextEditor::BaseTextEditor
 {
     Q_PROPERTY(QString source READ source WRITE setSource)
+    Q_PROPERTY(QString diffBaseDirectory READ diffBaseDirectory WRITE setDiffBaseDirectory)
     Q_PROPERTY(QTextCodec *codec READ codec WRITE setCodec)
     Q_OBJECT
 protected:
@@ -105,6 +106,10 @@ public:
     QTextCodec *codec() const;
     void setCodec(QTextCodec *);
 
+    // Base directory for diff views
+    QString diffBaseDirectory() const;
+    void setDiffBaseDirectory(const QString &d);
+
     bool isModified() const;
 
     EditorContentType contentType() const;
@@ -137,7 +142,7 @@ public:
     // editor if one has a call consisting of working directory and file arguments.
     // ('git diff XX' -> 'XX' , 'git diff XX file' -> 'XX/file').
     static QString getSource(const QString &workingDirectory, const QString &fileName);
-    static QString getSource(const QString &workingDirectory, const QStringList &fileNames);   
+    static QString getSource(const QString &workingDirectory, const QStringList &fileNames);
     // Convenience functions to determine an title/id to identify the editor
     // from the arguments (','-joined arguments or directory).
     static QString getTitleId(const QString &workingDirectory, const QStringList &fileNames);
@@ -171,8 +176,8 @@ private slots:
 
 protected:
     /* A helper that can be used to locate a file in a diff in case it
-     * is relative. Tries to derive the directory from source and
-     * version control. */
+     * is relative. Tries to derive the directory from base directory,
+     * source and version control. */
     QString findDiffFile(const QString &f, Core::IVersionControl *control = 0) const;
 
 private:
-- 
GitLab