diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp
index cca432cbabcab96b411761ec3c26415a62aded10..c7e4468b7b359386510f1f0642454ffcfe698072 100644
--- a/src/plugins/git/gitclient.cpp
+++ b/src/plugins/git/gitclient.cpp
@@ -251,7 +251,11 @@ void GitClient::status(const QString &workingDirectory)
     // @TODO: Use "--no-color" once it is supported
     QStringList statusArgs(QLatin1String("status"));
     statusArgs << QLatin1String("-u");
-    executeGit(workingDirectory, statusArgs, 0, true);
+    VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
+    outwin->setRepository(workingDirectory);
+    GitCommand *command = executeGit(workingDirectory, statusArgs, 0, true);
+    connect(command, SIGNAL(finished(bool,QVariant)), outwin, SLOT(clearRepository()),
+            Qt::QueuedConnection);
 }
 
 void GitClient::log(const QString &workingDirectory, const QStringList &fileNames)
diff --git a/src/plugins/mercurial/mercurialclient.cpp b/src/plugins/mercurial/mercurialclient.cpp
index fa5321630e553a94179ec79f564bdbb293abdc86..45001d7191aa3d6fb0cf8c45143f6e47e51aaabd 100644
--- a/src/plugins/mercurial/mercurialclient.cpp
+++ b/src/plugins/mercurial/mercurialclient.cpp
@@ -250,7 +250,11 @@ void MercurialClient::status(const QString &workingDir, const QString &file)
     QStringList args(QLatin1String("status"));
     if (!file.isEmpty())
         args.append(file);
+    VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
+    outwin->setRepository(workingDir);
     QSharedPointer<HgTask> job(new HgTask(workingDir, args, false));
+    connect(job.data(), SIGNAL(succeeded(QVariant)), outwin, SLOT(clearRepository()),
+            Qt::QueuedConnection);
     enqueueJob(job);
 }
 
diff --git a/src/plugins/perforce/perforceeditor.cpp b/src/plugins/perforce/perforceeditor.cpp
index 2c3621e523684420c3b3b17dd23963140b4efdfb..67f90446f912342785c7a21605fcf4630c4f4ed9 100644
--- a/src/plugins/perforce/perforceeditor.cpp
+++ b/src/plugins/perforce/perforceeditor.cpp
@@ -145,7 +145,7 @@ QString PerforceEditor::fileNameFromDiffSpecification(const QTextBlock &inBlock)
             if (revisionPos != -1 && revisionPos < diffFileName.length() - 1)
                 diffFileName.truncate(revisionPos);
             // Ask plugin to map back
-            const QString fileName = m_plugin->fileNameFromPerforceName(diffFileName.trimmed(), &errorMessage);
+            const QString fileName = m_plugin->fileNameFromPerforceName(diffFileName.trimmed(), false, &errorMessage);
             if (fileName.isEmpty())
                 qWarning("%s", qPrintable(errorMessage));
             return fileName;
diff --git a/src/plugins/perforce/perforceplugin.cpp b/src/plugins/perforce/perforceplugin.cpp
index b95e69596ada777239273ea14478c2ed024571ef..0ab8b079f51ee4298d92cd2ddbd20743a9b3fe5d 100644
--- a/src/plugins/perforce/perforceplugin.cpp
+++ b/src/plugins/perforce/perforceplugin.cpp
@@ -544,8 +544,29 @@ void PerforcePlugin::updateCheckout(const QString &workingDir, const QStringList
 
 void PerforcePlugin::printOpenedFileList()
 {
-    runP4Cmd(m_settings.topLevel(), QStringList(QLatin1String("opened")),
-             CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow);
+    const PerforceResponse perforceResponse
+            = runP4Cmd(m_settings.topLevel(), QStringList(QLatin1String("opened")),
+                       CommandToWindow|StdErrToWindow|ErrorToWindow);
+    if (perforceResponse.error || perforceResponse.stdOut.isEmpty())
+        return;
+    // reformat "//depot/file.cpp#1 - description" into "file.cpp # - description"
+    // for context menu opening to work. This produces absolute paths, then.
+    VCSBase::VCSBaseOutputWindow *outWin = VCSBase::VCSBaseOutputWindow::instance();
+    QString errorMessage;
+    QString mapped;
+    const QChar delimiter = QLatin1Char('#');
+    foreach (const QString &line, perforceResponse.stdOut.split(QLatin1Char('\n'))) {
+        mapped.clear();
+        const int delimiterPos = line.indexOf(delimiter);
+        if (delimiterPos > 0)
+            mapped = fileNameFromPerforceName(line.left(delimiterPos), true, &errorMessage);
+        if (mapped.isEmpty()) {
+            outWin->appendSilently(line);
+        } else {
+            outWin->appendSilently(mapped + QLatin1Char(' ') + line.mid(delimiterPos));
+        }
+    }
+    outWin->popup();
 }
 
 void PerforcePlugin::startSubmitProject()
@@ -1282,6 +1303,7 @@ static inline QString msgWhereFailed(const QString & file, const QString &why)
 
 // Map a perforce name "//xx" to its real name in the file system
 QString PerforcePlugin::fileNameFromPerforceName(const QString& perforceName,
+                                                 bool quiet,
                                                  QString *errorMessage) const
 {
     // All happy, already mapped
@@ -1290,8 +1312,10 @@ QString PerforcePlugin::fileNameFromPerforceName(const QString& perforceName,
     // "where" remaps the file to client file tree
     QStringList args;
     args << QLatin1String("where") << perforceName;
-    const PerforceResponse response = runP4Cmd(m_settings.topLevelSymLinkTarget(), args,
-                                               RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow);
+    unsigned flags = RunFullySynchronous;
+    if (!quiet)
+        flags |= CommandToWindow|StdErrToWindow|ErrorToWindow;
+    const PerforceResponse response = runP4Cmd(m_settings.topLevelSymLinkTarget(), args, flags);
     if (response.error) {
         *errorMessage = msgWhereFailed(perforceName, response.message);
         return QString::null;
diff --git a/src/plugins/perforce/perforceplugin.h b/src/plugins/perforce/perforceplugin.h
index bd1813a93a4393142a6046e4f1ccc64cfac15ce2..f3a60d02e4aadc99598d64dc60125ca4f733be6f 100644
--- a/src/plugins/perforce/perforceplugin.h
+++ b/src/plugins/perforce/perforceplugin.h
@@ -96,7 +96,9 @@ public:
     void setSettings(const Settings &s);
 
     // Map a perforce name "//xx" to its real name in the file system
-    QString fileNameFromPerforceName(const QString& perforceName, QString *errorMessage) const;
+    QString fileNameFromPerforceName(const QString& perforceName,
+                                     bool quiet,
+                                     QString *errorMessage) const;
 
 public slots:
     void describe(const QString &source, const QString &n);
diff --git a/src/plugins/subversion/subversionplugin.cpp b/src/plugins/subversion/subversionplugin.cpp
index 545c5cf16ac0926814d98da2f453f7284ae29f2d..e648f3163b112b7a6a7938378f8f30993d6cb1cb 100644
--- a/src/plugins/subversion/subversionplugin.cpp
+++ b/src/plugins/subversion/subversionplugin.cpp
@@ -748,7 +748,10 @@ void SubversionPlugin::projectStatus()
     QTC_ASSERT(state.hasProject(), return);
     QStringList args(QLatin1String("status"));
     args += state.relativeCurrentProject();
+    VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
+    outwin->setRepository(state.currentProjectTopLevel());
     runSvn(state.currentProjectTopLevel(), args, m_settings.timeOutMS(), true);
+    outwin->clearRepository();
 }
 
 void SubversionPlugin::describe(const QString &source, const QString &changeNr)
diff --git a/src/plugins/vcsbase/vcsbaseoutputwindow.cpp b/src/plugins/vcsbase/vcsbaseoutputwindow.cpp
index 90ee006285c20e0e6b474032195ad534fa49b141..0f5812920f959ac4137579c9fb5d127b1fc23e8d 100644
--- a/src/plugins/vcsbase/vcsbaseoutputwindow.cpp
+++ b/src/plugins/vcsbase/vcsbaseoutputwindow.cpp
@@ -30,28 +30,44 @@
 #include "vcsbaseoutputwindow.h"
 
 #include <utils/qtcassert.h>
+#include <coreplugin/editormanager/editormanager.h>
 
 #include <QtGui/QPlainTextEdit>
 #include <QtGui/QTextCharFormat>
 #include <QtGui/QContextMenuEvent>
+#include <QtGui/QTextBlock>
 #include <QtGui/QMenu>
 #include <QtGui/QAction>
+#include <QtGui/QTextDocument>
+#include <QtGui/QTextBlockUserData>
 
 #include <QtCore/QPointer>
 #include <QtCore/QTextCodec>
 #include <QtCore/QTime>
+#include <QtCore/QPoint>
+#include <QtCore/QFileInfo>
 
 namespace VCSBase {
 
 namespace Internal {
 
+// Store repository along with text blocks
+class RepositoryUserData : public QTextBlockUserData    {
+public:
+    explicit RepositoryUserData(const QString &repo) : m_repository(repo) {}
+    const QString &repository() const { return m_repository; }
+
+private:
+    const QString m_repository;
+};
+
 // A plain text edit with a special context menu containing "Clear" and
 // and functions to append specially formatted entries.
 class OutputWindowPlainTextEdit : public QPlainTextEdit {
 public:
     explicit OutputWindowPlainTextEdit(QWidget *parent);
 
-    void appendLines(QString s);
+    void appendLines(QString s, const QString &repository = QString());
     // Append red error text and pop up.
     void appendError(const QString &text);
     // Append warning error text and pop up.
@@ -63,6 +79,8 @@ protected:
     virtual void contextMenuEvent(QContextMenuEvent *event);
 
 private:
+    QString identifierUnderCursor(const QPoint &pos, QString *repository = 0) const;
+
     const QTextCharFormat m_defaultFormat;
     QTextCharFormat m_errorFormat;
     QTextCharFormat m_warningFormat;
@@ -83,27 +101,100 @@ OutputWindowPlainTextEdit::OutputWindowPlainTextEdit(QWidget *parent) :
     m_commandFormat.setFontWeight(QFont::Bold);
 }
 
+// Search back for beginning of word
+static inline int firstWordCharacter(const QString &s, int startPos)
+{
+    for ( ; startPos >= 0 ; startPos--) {
+        if (s.at(startPos).isSpace())
+            return startPos + 1;
+    }
+    return 0;
+}
+
+QString OutputWindowPlainTextEdit::identifierUnderCursor(const QPoint &widgetPos, QString *repository) const
+{
+    if (repository)
+        repository->clear();
+    // Get the blank-delimited word under cursor. Note that
+    // using "SelectWordUnderCursor" does not work since it breaks
+    // at delimiters like '/'. Get the whole line
+    QTextCursor cursor = cursorForPosition(widgetPos);
+    const int cursorDocumentPos = cursor.position();
+    cursor.select(QTextCursor::BlockUnderCursor);
+    if (!cursor.hasSelection())
+        return QString();
+    QString block = cursor.selectedText();
+    // Determine cursor position within line and find blank-delimited word
+    const int cursorPos = cursorDocumentPos - cursor.block().position();
+    const int blockSize = block.size();
+    if (cursorPos < 0 || cursorPos >= blockSize || block.at(cursorPos).isSpace())
+        return QString();
+    // Retrieve repository if desired
+    if (repository)
+        if (QTextBlockUserData *data = cursor.block().userData())
+            *repository = static_cast<const RepositoryUserData*>(data)->repository();
+    // Find first non-space character of word and find first non-space character past
+    const int startPos = firstWordCharacter(block, cursorPos);
+    int endPos = cursorPos;
+    for ( ; endPos < blockSize && !block.at(endPos).isSpace(); endPos++) ;
+    return endPos > startPos ? block.mid(startPos, endPos - startPos) : QString();
+}
+
 void OutputWindowPlainTextEdit::contextMenuEvent(QContextMenuEvent *event)
 {
     QMenu *menu = createStandardContextMenu();
+    // Add 'open file'
+    QString repository;
+    const QString token = identifierUnderCursor(event->pos(), &repository);
+    QAction *openAction = 0;
+    if (!token.isEmpty()) {
+        // Check for a file, expand via repository if relative
+        QFileInfo fi(token);
+        if (!repository.isEmpty() && !fi.isFile() && fi.isRelative())
+            fi = QFileInfo(repository + QLatin1Char('/') + token);
+        if (fi.isFile())  {
+            menu->addSeparator();
+            openAction = menu->addAction(VCSBaseOutputWindow::tr("Open \"%1\"").arg(fi.fileName()));
+            openAction->setData(fi.absoluteFilePath());
+        }
+    }
+    // Add 'clear'
     menu->addSeparator();
     QAction *clearAction = menu->addAction(VCSBaseOutputWindow::tr("Clear"));
-    connect(clearAction, SIGNAL(triggered()), this, SLOT(clear()));
-    menu->exec(event->globalPos());
+
+    // Run
+    QAction *action = menu->exec(event->globalPos());
+    if (action) {
+        if (action == clearAction) {
+            clear();
+            return;
+        }
+        if (action == openAction) {
+            const QString fileName = action->data().toString();
+            Core::EditorManager::instance()->openEditor(fileName);
+        }
+    }
     delete menu;
 }
 
-void OutputWindowPlainTextEdit::appendLines(QString s)
+void OutputWindowPlainTextEdit::appendLines(QString s, const QString &repository)
 {
     if (s.isEmpty())
         return;
     // Avoid additional new line character generated by appendPlainText
     if (s.endsWith(QLatin1Char('\n')))
         s.truncate(s.size() - 1);
+    const int previousLineCount = document()->lineCount();
     appendPlainText(s);
     // Scroll down
     moveCursor(QTextCursor::End);
     ensureCursorVisible();
+    if (!repository.isEmpty()) {
+        // Associate repository with new data.
+        QTextBlock block = document()->findBlockByLineNumber(previousLineCount);
+        for ( ; block.isValid(); block = block.next())
+            block.setUserData(new RepositoryUserData(repository));
+    }
 }
 
 void OutputWindowPlainTextEdit::appendError(const QString &text)
@@ -135,6 +226,7 @@ void OutputWindowPlainTextEdit::appendCommand(const QString &text)
 struct VCSBaseOutputWindowPrivate {
     static VCSBaseOutputWindow *instance;
     QPointer<Internal::OutputWindowPlainTextEdit> plainTextEdit;
+    QString repository;
 };
 
 VCSBaseOutputWindow *VCSBaseOutputWindowPrivate::instance = 0;
@@ -236,7 +328,7 @@ void VCSBaseOutputWindow::setData(const QByteArray &data)
 void VCSBaseOutputWindow::appendSilently(const QString &text)
 {
     QTC_ASSERT(d->plainTextEdit, return)
-    d->plainTextEdit->appendLines(text);
+    d->plainTextEdit->appendLines(text, d->repository);
 }
 
 void VCSBaseOutputWindow::append(const QString &text)
@@ -292,4 +384,19 @@ VCSBaseOutputWindow *VCSBaseOutputWindow::instance()
     return VCSBaseOutputWindowPrivate::instance;
 }
 
+QString VCSBaseOutputWindow::repository() const
+{
+    return d->repository;
+}
+
+void VCSBaseOutputWindow::setRepository(const QString &r)
+{
+    d->repository = r;
+}
+
+void VCSBaseOutputWindow::clearRepository()
+{
+    d->repository.clear();
+}
+
 } // namespace VCSBase
diff --git a/src/plugins/vcsbase/vcsbaseoutputwindow.h b/src/plugins/vcsbase/vcsbaseoutputwindow.h
index b7baf202f7b7b64d75c510d07caf6dac96e92e88..d669a3e31554cbae1f18d9466cdfc17da4cec317 100644
--- a/src/plugins/vcsbase/vcsbaseoutputwindow.h
+++ b/src/plugins/vcsbase/vcsbaseoutputwindow.h
@@ -41,11 +41,16 @@ struct VCSBaseOutputWindowPrivate;
 /* Common OutputWindow for Version Control System command and other output.
  * Installed by the base plugin and accessible for the other plugins
  * via static instance()-accessor. Provides slots to append output with
- * special formatting. */
+ * special formatting.
+ * It is possible to associate a repository with plain log text, enabling
+ * an "Open" context menu action over relative file name tokens in the text
+ * (absolute paths will also work). This can be used for "status" logs,
+ * showing modified file names, allowing the user to open them. */
 
 class VCSBASE_EXPORT VCSBaseOutputWindow : public Core::IOutputPane
 {
     Q_OBJECT
+    Q_PROPERTY(QString repository READ repository WRITE setRepository)
 public:
     virtual ~VCSBaseOutputWindow();
 
@@ -70,7 +75,12 @@ public:
 
     static VCSBaseOutputWindow *instance();
 
+    QString repository() const;
+
 public slots:
+    void setRepository(const QString &);
+    void clearRepository();
+
     // Set the whole text.
     void setText(const QString &text);
     // Set text from QProcess' output data using the Locale's converter.