From 491a27163a9a843356a686ea405723608b10aa11 Mon Sep 17 00:00:00 2001
From: Friedemann Kleint <Friedemann.Kleint@nokia.com>
Date: Thu, 24 Mar 2011 15:44:39 +0100
Subject: [PATCH] VCS[git]: Add 'Revert this chunk' context menu option to diff
 view.

Implement in git. Add infrastructure to revert single chhunks
by using patch -R. Currently only implemented in git since
only that has functionality to re-run diff.

Rubber-stamped-by: hunger <tobias.hunger@nokia.com>
---
 src/plugins/git/gitclient.cpp              |   4 +
 src/plugins/git/gitclient.h                |   2 +
 src/plugins/vcsbase/commonsettingspage.cpp |   6 ++
 src/plugins/vcsbase/commonsettingspage.ui  |  56 ++++++----
 src/plugins/vcsbase/commonvcssettings.cpp  |   9 +-
 src/plugins/vcsbase/commonvcssettings.h    |   2 +
 src/plugins/vcsbase/vcsbaseeditor.cpp      | 115 ++++++++++++++++++++-
 src/plugins/vcsbase/vcsbaseeditor.h        |  25 +++++
 src/plugins/vcsbase/vcsbaseplugin.cpp      |  49 +++++++++
 src/plugins/vcsbase/vcsbaseplugin.h        |   4 +
 10 files changed, 251 insertions(+), 21 deletions(-)

diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp
index 3ab49446615..1ed8d8de635 100644
--- a/src/plugins/git/gitclient.cpp
+++ b/src/plugins/git/gitclient.cpp
@@ -521,6 +521,8 @@ void GitClient::diff(const QString &workingDirectory,
 
         editor = createVCSEditor(editorId, title,
                                  workingDirectory, true, "originalFileName", workingDirectory, argWidget);
+        connect(editor, SIGNAL(diffChunkReverted(VCSBase::DiffChunk)), argWidget, SLOT(redoCommand()));
+        editor->setRevertDiffChunkEnabled(true);
     }
     editor->setDiffBaseDirectory(workingDirectory);
 
@@ -575,6 +577,8 @@ void GitClient::diff(const QString &workingDirectory,
         userDiffArgs = argWidget->arguments();
 
         editor = createVCSEditor(editorId, title, sourceFile, true, "originalFileName", sourceFile, argWidget);
+        connect(editor, SIGNAL(diffChunkReverted(VCSBase::DiffChunk)), argWidget, SLOT(redoCommand()));
+        editor->setRevertDiffChunkEnabled(true);
     }
 
     QStringList cmdArgs;
diff --git a/src/plugins/git/gitclient.h b/src/plugins/git/gitclient.h
index b6aef5f2da8..c1a67c52e91 100644
--- a/src/plugins/git/gitclient.h
+++ b/src/plugins/git/gitclient.h
@@ -306,6 +306,8 @@ public:
                            const QStringList &args);
 
     virtual QStringList arguments() const = 0;
+
+public slots:
     virtual void redoCommand() = 0;
 
 protected slots:
diff --git a/src/plugins/vcsbase/commonsettingspage.cpp b/src/plugins/vcsbase/commonsettingspage.cpp
index 6cdeb8b0a71..1e6f0d13b61 100644
--- a/src/plugins/vcsbase/commonsettingspage.cpp
+++ b/src/plugins/vcsbase/commonsettingspage.cpp
@@ -58,6 +58,10 @@ CommonSettingsWidget::CommonSettingsWidget(QWidget *parent) :
     m_ui->nickNameFieldsFileChooser->setExpectedKind(Utils::PathChooser::File);
     m_ui->nickNameMailMapChooser->setExpectedKind(Utils::PathChooser::File);
     m_ui->sshPromptChooser->setExpectedKind(Utils::PathChooser::ExistingCommand);
+    const QString patchToolTip = tr("Command used for reverting diff chunks");
+    m_ui->patchCommandLabel->setToolTip(patchToolTip);
+    m_ui->patchChooser->setToolTip(patchToolTip);
+    m_ui->patchChooser->setExpectedKind(Utils::PathChooser::ExistingCommand);
 }
 
 CommonSettingsWidget::~CommonSettingsWidget()
@@ -74,6 +78,7 @@ CommonVcsSettings CommonSettingsWidget::settings() const
     rc.lineWrap= m_ui->lineWrapCheckBox->isChecked();
     rc.lineWrapWidth = m_ui->lineWrapSpinBox->value();
     rc.sshPasswordPrompt = m_ui->sshPromptChooser->path();
+    rc.patchCommand = m_ui->patchChooser->path();
     return rc;
 }
 
@@ -85,6 +90,7 @@ void CommonSettingsWidget::setSettings(const CommonVcsSettings &s)
     m_ui->lineWrapCheckBox->setChecked(s.lineWrap);
     m_ui->lineWrapSpinBox->setValue(s.lineWrapWidth);
     m_ui->sshPromptChooser->setPath(s.sshPasswordPrompt);
+    m_ui->patchChooser->setPath(s.patchCommand);
 }
 
 QString CommonSettingsWidget::searchKeyWordMatchString() const
diff --git a/src/plugins/vcsbase/commonsettingspage.ui b/src/plugins/vcsbase/commonsettingspage.ui
index c15f9cd96f3..e4a8cdd6b8c 100644
--- a/src/plugins/vcsbase/commonsettingspage.ui
+++ b/src/plugins/vcsbase/commonsettingspage.ui
@@ -2,15 +2,10 @@
 <ui version="4.0">
  <class>CommonSettingsPage</class>
  <widget class="QWidget" name="CommonSettingsPage">
-  <property name="geometry">
-   <rect>
-    <x>0</x>
-    <y>0</y>
-    <width>338</width>
-    <height>166</height>
-   </rect>
-  </property>
   <layout class="QFormLayout" name="formLayout">
+   <property name="fieldGrowthPolicy">
+    <enum>QFormLayout::ExpandingFieldsGrow</enum>
+   </property>
    <item row="0" column="0">
     <widget class="QCheckBox" name="lineWrapCheckBox">
      <property name="text">
@@ -56,12 +51,15 @@
       <string>An executable which is called with the submit message in a temporary file as first argument. It should return with an exit != 0 and a message on standard error to indicate failure.</string>
      </property>
      <property name="text">
-      <string>Submit message check script:</string>
+      <string>Submit message &amp;check script:</string>
+     </property>
+     <property name="buddy">
+      <cstring>submitMessageCheckScriptChooser</cstring>
      </property>
     </widget>
    </item>
    <item row="2" column="1">
-    <widget class="Utils::PathChooser" name="submitMessageCheckScriptChooser" native="true"/>
+    <widget class="Utils::PathChooser" name="submitMessageCheckScriptChooser"/>
    </item>
    <item row="3" column="0">
     <widget class="QLabel" name="nickNameMailMapLabel">
@@ -70,12 +68,15 @@
 name &lt;email&gt; alias &lt;email&gt;</string>
      </property>
      <property name="text">
-      <string>User/alias configuration file:</string>
+      <string>User/&amp;alias configuration file:</string>
+     </property>
+     <property name="buddy">
+      <cstring>nickNameMailMapChooser</cstring>
      </property>
     </widget>
    </item>
    <item row="3" column="1">
-    <widget class="Utils::PathChooser" name="nickNameMailMapChooser" native="true"/>
+    <widget class="Utils::PathChooser" name="nickNameMailMapChooser"/>
    </item>
    <item row="4" column="0">
     <widget class="QLabel" name="nickNameFieldsFileLabel">
@@ -83,12 +84,18 @@ name &lt;email&gt; alias &lt;email&gt;</string>
       <string>A simple file containing lines with field names like &quot;Reviewed-By:&quot; which will be added below the submit editor.</string>
      </property>
      <property name="text">
-      <string>User fields configuration file:</string>
+      <string>User &amp;fields configuration file:</string>
+     </property>
+     <property name="buddy">
+      <cstring>nickNameFieldsFileChooser</cstring>
      </property>
     </widget>
    </item>
    <item row="4" column="1">
-    <widget class="Utils::PathChooser" name="nickNameFieldsFileChooser" native="true"/>
+    <widget class="Utils::PathChooser" name="nickNameFieldsFileChooser"/>
+   </item>
+   <item row="5" column="1">
+    <widget class="Utils::PathChooser" name="sshPromptChooser"/>
    </item>
    <item row="6" column="0" colspan="2">
     <spacer name="verticalSpacer">
@@ -106,6 +113,19 @@ name &lt;email&gt; alias &lt;email&gt;</string>
      </property>
     </spacer>
    </item>
+   <item row="7" column="0">
+    <widget class="QLabel" name="patchCommandLabel">
+     <property name="text">
+      <string>&amp;Patch command:</string>
+     </property>
+     <property name="buddy">
+      <cstring>patchChooser</cstring>
+     </property>
+    </widget>
+   </item>
+   <item row="7" column="1">
+    <widget class="Utils::PathChooser" name="patchChooser"/>
+   </item>
    <item row="5" column="0">
     <widget class="QLabel" name="sshPromptLabel">
      <property name="toolTip">
@@ -113,13 +133,13 @@ name &lt;email&gt; alias &lt;email&gt;</string>
 should a repository require SSH-authentication (see documentation on SSH and the environment variable SSH_ASKPASS).</string>
      </property>
      <property name="text">
-      <string>SSH prompt command:</string>
+      <string>&amp;SSH prompt command:</string>
+     </property>
+     <property name="buddy">
+      <cstring>sshPromptChooser</cstring>
      </property>
     </widget>
    </item>
-   <item row="5" column="1">
-    <widget class="Utils::PathChooser" name="sshPromptChooser" native="true"/>
-   </item>
   </layout>
  </widget>
  <customwidgets>
diff --git a/src/plugins/vcsbase/commonvcssettings.cpp b/src/plugins/vcsbase/commonvcssettings.cpp
index fdb9ec82479..172af681f21 100644
--- a/src/plugins/vcsbase/commonvcssettings.cpp
+++ b/src/plugins/vcsbase/commonvcssettings.cpp
@@ -43,6 +43,8 @@ static const char submitMessageCheckScriptKeyC[] = "SubmitMessageCheckScript";
 static const char lineWrapKeyC[] = "LineWrap";
 static const char lineWrapWidthKeyC[] = "LineWrapWidth";
 static const char sshPasswordPromptKeyC[] = "SshPasswordPrompt";
+static const char patchCommandKeyC[] = "PatchCommand";
+static const char patchCommandDefaultC[] = "patch";
 
 static const int lineWrapWidthDefault = 72;
 static const bool lineWrapDefault = true;
@@ -65,6 +67,7 @@ namespace Internal {
 
 CommonVcsSettings::CommonVcsSettings() :
     sshPasswordPrompt(sshPasswordPromptDefault()),
+    patchCommand(QLatin1String(patchCommandDefaultC)),
     lineWrap(lineWrapDefault),
     lineWrapWidth(lineWrapWidthDefault)
 {
@@ -78,6 +81,7 @@ void CommonVcsSettings::toSettings(QSettings *s) const
     s->setValue(QLatin1String(submitMessageCheckScriptKeyC), submitMessageCheckScript);
     s->setValue(QLatin1String(lineWrapKeyC), lineWrap);
     s->setValue(QLatin1String(lineWrapWidthKeyC), lineWrapWidth);
+    s->setValue(QLatin1String(patchCommandKeyC), patchCommand);
     // Do not store the default setting to avoid clobbering the environment.
     if (sshPasswordPrompt != sshPasswordPromptDefault()) {
         s->setValue(QLatin1String(sshPasswordPromptKeyC), sshPasswordPrompt);
@@ -96,6 +100,7 @@ void CommonVcsSettings::fromSettings(QSettings *s)
     lineWrap = s->value(QLatin1String(lineWrapKeyC), lineWrapDefault).toBool();
     lineWrapWidth = s->value(QLatin1String(lineWrapWidthKeyC), lineWrapWidthDefault).toInt();
     sshPasswordPrompt = s->value(QLatin1String(sshPasswordPromptKeyC), sshPasswordPromptDefault()).toString();
+    patchCommand = s->value(QLatin1String(patchCommandKeyC), QLatin1String(patchCommandDefaultC)).toString();
     s->endGroup();
 }
 
@@ -106,7 +111,8 @@ bool CommonVcsSettings::equals(const CommonVcsSettings &rhs) const
            && nickNameMailMap == rhs.nickNameMailMap
            && nickNameFieldListFile == rhs.nickNameFieldListFile
            && submitMessageCheckScript == rhs.submitMessageCheckScript
-           && sshPasswordPrompt == rhs.sshPasswordPrompt;
+           && sshPasswordPrompt == rhs.sshPasswordPrompt
+           && patchCommand == rhs.patchCommand;
 }
 
 QDebug operator<<(QDebug d,const CommonVcsSettings& s)
@@ -117,6 +123,7 @@ QDebug operator<<(QDebug d,const CommonVcsSettings& s)
             << "' nickNameFieldListFile='" << s.nickNameFieldListFile
             << "'submitMessageCheckScript='" << s.submitMessageCheckScript
             << "'sshPasswordPrompt='" << s.sshPasswordPrompt
+            << "'patchCommand='" << s.patchCommand
             << "'\n";
     return d;
 }
diff --git a/src/plugins/vcsbase/commonvcssettings.h b/src/plugins/vcsbase/commonvcssettings.h
index f7e8aef9337..8f182e27e7f 100644
--- a/src/plugins/vcsbase/commonvcssettings.h
+++ b/src/plugins/vcsbase/commonvcssettings.h
@@ -58,6 +58,8 @@ struct CommonVcsSettings
     // Executable run to graphically prompt for a SSH-password.
     QString sshPasswordPrompt;
 
+    QString patchCommand;
+
     bool lineWrap;
     int lineWrapWidth;
 
diff --git a/src/plugins/vcsbase/vcsbaseeditor.cpp b/src/plugins/vcsbase/vcsbaseeditor.cpp
index 69b7b24a18a..4e5da7bf9d0 100644
--- a/src/plugins/vcsbase/vcsbaseeditor.cpp
+++ b/src/plugins/vcsbase/vcsbaseeditor.cpp
@@ -36,6 +36,8 @@
 #include "baseannotationhighlighter.h"
 #include "vcsbasetextdocument.h"
 #include "vcsbaseconstants.h"
+#include "vcsbaseoutputwindow.h"
+#include "vcsbaseplugin.h"
 
 #include <coreplugin/editormanager/editormanager.h>
 #include <coreplugin/ifile.h>
@@ -53,6 +55,7 @@
 
 #include <QtCore/QDebug>
 #include <QtCore/QFileInfo>
+#include <QtCore/QFile>
 #include <QtCore/QProcess>
 #include <QtCore/QRegExp>
 #include <QtCore/QSet>
@@ -69,9 +72,27 @@
 #include <QtGui/QToolBar>
 #include <QtGui/QClipboard>
 #include <QtGui/QApplication>
+#include <QtGui/QMessageBox>
 
 namespace VCSBase {
 
+bool DiffChunk::isValid() const
+{
+    return !fileName.isEmpty() && !chunk.isEmpty();
+}
+
+QByteArray DiffChunk::asPatch() const
+{
+    const QByteArray fileNameBA = QFile::encodeName(fileName);
+    QByteArray rc = "--- ";
+    rc += fileNameBA;
+    rc += "\n+++ ";
+    rc += fileNameBA;
+    rc += '\n';
+    rc += chunk;
+    return rc;
+}
+
 // VCSBaseEditor: An editor with no support for duplicates.
 // Creates a browse combo in the toolbar for diff output.
 // It also mirrors the signals of the VCSBaseEditor since the editor
@@ -161,6 +182,7 @@ struct VCSBaseEditorWidgetPrivate
     bool m_fileLogAnnotateEnabled;
     TextEditor::BaseTextEditor *m_editor;
     QWidget *m_configurationWidget;
+    bool m_revertChunkEnabled;
 };
 
 VCSBaseEditorWidgetPrivate::VCSBaseEditorWidgetPrivate(const VCSBaseEditorParameters *type)  :
@@ -170,7 +192,8 @@ VCSBaseEditorWidgetPrivate::VCSBaseEditorWidgetPrivate(const VCSBaseEditorParame
     m_copyRevisionTextFormat(VCSBaseEditorWidget::tr("Copy \"%1\"")),
     m_fileLogAnnotateEnabled(false),
     m_editor(0),
-    m_configurationWidget(0)
+    m_configurationWidget(0),
+    m_revertChunkEnabled(false)
 {
 }
 
@@ -444,7 +467,9 @@ void VCSBaseEditorWidget::contextMenuEvent(QContextMenuEvent *e)
 {
     QMenu *menu = createStandardContextMenu();
     // 'click on change-interaction'
-    if (d->m_parameters->type == LogOutput || d->m_parameters->type == AnnotateOutput) {
+    switch (d->m_parameters->type) {
+    case LogOutput:
+    case AnnotateOutput:
         d->m_currentChange = changeUnderCursor(cursorForPosition(e->pos()));
         if (!d->m_currentChange.isEmpty()) {
             switch (d->m_parameters->type) {
@@ -471,6 +496,18 @@ void VCSBaseEditorWidget::contextMenuEvent(QContextMenuEvent *e)
                 break;
             }         // switch type
         }             // has current change
+        break;
+    case DiffOutput: {
+        menu->addSeparator();
+        QAction *revertAction = menu->addAction(tr("Revert Chunk..."));
+        const DiffChunk chunk = diffChunk(cursorForPosition(e->pos()));
+        revertAction->setEnabled(canRevertDiffChunk(chunk));
+        revertAction->setData(qVariantFromValue(chunk));
+        connect(revertAction, SIGNAL(triggered()), this, SLOT(slotRevertDiffChunk()));
+    }
+        break;
+    default:
+        break;
     }
     menu->exec(e->globalPos());
     delete menu;
@@ -643,6 +680,40 @@ void VCSBaseEditorWidget::jumpToChangeFromDiff(QTextCursor cursor)
         editor->gotoLine(chunkStart + lineCount);
 }
 
+// cut out chunk and determine file name.
+DiffChunk VCSBaseEditorWidget::diffChunk(QTextCursor cursor) const
+{
+    QTC_ASSERT(d->m_parameters->type == DiffOutput, return DiffChunk(); )
+    DiffChunk rc;
+    // Search back for start of chunk.
+    QTextBlock block = cursor.block();
+    int chunkStart = 0;
+    for ( ; block.isValid() ; block = block.previous()) {
+        if (checkChunkLine(block.text(), &chunkStart)) {
+            break;
+        }
+    }
+    if (!chunkStart || !block.isValid())
+        return rc;
+    rc.fileName = fileNameFromDiffSpecification(block);
+    if (rc.fileName.isEmpty())
+        return rc;
+    // Concatenate chunk and convert
+    QString unicode = block.text();
+    for (block = block.next() ; block.isValid() ; block = block.next()) {
+        const QString line = block.text();
+        if (checkChunkLine(line, &chunkStart)) {
+            break;
+        } else {
+            unicode += line;
+            unicode += QLatin1Char('\n');
+        }
+    }
+    const QTextCodec *cd = textCodec();
+    rc.chunk = cd ? cd->fromUnicode(unicode) : unicode.toLocal8Bit();
+    return rc;
+}
+
 void VCSBaseEditorWidget::setPlainTextData(const QByteArray &data)
 {
     if (data.size() > Core::EditorManager::maxTextFileSize()) {
@@ -901,6 +972,46 @@ QStringList VCSBaseEditorWidget::annotationPreviousVersions(const QString &) con
     return QStringList();
 }
 
+bool VCSBaseEditorWidget::isRevertDiffChunkEnabled() const
+{
+    return d->m_revertChunkEnabled;
+}
+
+void VCSBaseEditorWidget::setRevertDiffChunkEnabled(bool e)
+{
+    d->m_revertChunkEnabled = e;
+}
+
+bool VCSBaseEditorWidget::canRevertDiffChunk(const DiffChunk &dc) const
+{
+    if (!isRevertDiffChunkEnabled() || !dc.isValid())
+        return false;
+    const QFileInfo fi(dc.fileName);
+    // Default implementation using patch.exe relies on absolute paths.
+    return fi.isFile() && fi.isAbsolute() && fi.isWritable();
+}
+
+// Default implementation of revert: Revert a chunk by piping it into patch
+// with '-R', assuming we got absolute paths from the VCS plugins.
+bool VCSBaseEditorWidget::revertDiffChunk(const DiffChunk &dc) const
+{
+    return VCSBasePlugin::runPatch(dc.asPatch(), QString(), 0, true);
+}
+
+void VCSBaseEditorWidget::slotRevertDiffChunk()
+{
+    const QAction *a = qobject_cast<QAction *>(sender());
+    QTC_ASSERT(a, return ; )
+    const DiffChunk chunk = qvariant_cast<DiffChunk>(a->data());
+    if (QMessageBox::No == QMessageBox::question(this, tr("Revert Chunk"),
+                                                  tr("Would you like to revert the chunk?"),
+                                                  QMessageBox::Yes|QMessageBox::No))
+        return;
+
+    if (revertDiffChunk(chunk))
+        emit diffChunkReverted(chunk);
+}
+
 } // namespace VCSBase
 
 #include "vcsbaseeditor.moc"
diff --git a/src/plugins/vcsbase/vcsbaseeditor.h b/src/plugins/vcsbase/vcsbaseeditor.h
index 296c81ae6e9..8418a3a5794 100644
--- a/src/plugins/vcsbase/vcsbaseeditor.h
+++ b/src/plugins/vcsbase/vcsbaseeditor.h
@@ -86,6 +86,16 @@ struct VCSBASE_EXPORT VCSBaseEditorParameters
     const char *extension;
 };
 
+class VCSBASE_EXPORT DiffChunk
+{
+public:
+    bool isValid() const;
+    QByteArray asPatch() const;
+
+    QString fileName;
+    QByteArray chunk;
+};
+
 // Base class for editors showing version control system output
 // of the type enumerated by EditorContentType.
 // The source property should contain the file or directory the log
@@ -99,6 +109,7 @@ class VCSBASE_EXPORT VCSBaseEditorWidget : public TextEditor::BaseTextEditorWidg
     Q_PROPERTY(QString annotateRevisionTextFormat READ annotateRevisionTextFormat WRITE setAnnotateRevisionTextFormat)
     Q_PROPERTY(QString copyRevisionTextFormat READ copyRevisionTextFormat WRITE setCopyRevisionTextFormat)
     Q_PROPERTY(bool isFileLogAnnotateEnabled READ isFileLogAnnotateEnabled WRITE setFileLogAnnotateEnabled)
+    Q_PROPERTY(bool revertDiffChunkEnabled READ isRevertDiffChunkEnabled WRITE setRevertDiffChunkEnabled)
     Q_OBJECT
 
 protected:
@@ -146,6 +157,10 @@ public:
     QString diffBaseDirectory() const;
     void setDiffBaseDirectory(const QString &d);
 
+    // Diff: Can revert?
+    bool isRevertDiffChunkEnabled() const;
+    void setRevertDiffChunkEnabled(bool e);
+
     bool isModified() const;
 
     EditorContentType contentType() const;
@@ -194,6 +209,7 @@ signals:
     // for LogOutput/AnnotateOutput content types.
     void describeRequested(const QString &source, const QString &change);
     void annotateRevisionRequested(const QString &source, const QString &change, int lineNumber);
+    void diffChunkReverted(const VCSBase::DiffChunk &dc);
 
 public slots:
     // Convenience slot to set data read from stdout, will use the
@@ -220,6 +236,7 @@ private slots:
     void slotDiffCursorPositionChanged();
     void slotAnnotateRevision();
     void slotCopyRevision();
+    void slotRevertDiffChunk();
 
 protected:
     /* A helper that can be used to locate a file in a diff in case it
@@ -227,6 +244,10 @@ protected:
      * source and version control. */
     QString findDiffFile(const QString &f, Core::IVersionControl *control = 0) const;
 
+    virtual bool canRevertDiffChunk(const DiffChunk &dc) const;
+    // Revert a patch chunk. Default implemenation uses patch.exe
+    virtual bool revertDiffChunk(const DiffChunk &dc) const;
+
 private:
     // Implement to return a set of change identifiers in
     // annotation mode
@@ -242,6 +263,8 @@ private:
     // Implement to return the previous version[s] of an annotation change
     // for "Annotate previous version"
     virtual QStringList annotationPreviousVersions(const QString &revision) const;
+    // cut out chunk and determine file name.
+    DiffChunk diffChunk(QTextCursor cursor) const;
 
     void jumpToChangeFromDiff(QTextCursor cursor);
     QAction *createDescribeAction(const QString &change);
@@ -253,4 +276,6 @@ private:
 
 } // namespace VCSBase
 
+Q_DECLARE_METATYPE(VCSBase::DiffChunk)
+
 #endif // VCSBASE_BASEEDITOR_H
diff --git a/src/plugins/vcsbase/vcsbaseplugin.cpp b/src/plugins/vcsbase/vcsbaseplugin.cpp
index 359e02b8684..24f28b86510 100644
--- a/src/plugins/vcsbase/vcsbaseplugin.cpp
+++ b/src/plugins/vcsbase/vcsbaseplugin.cpp
@@ -49,6 +49,7 @@
 #include <projectexplorer/project.h>
 #include <utils/qtcassert.h>
 #include <utils/synchronousprocess.h>
+#include <utils/environment.h>
 
 #include <QtCore/QDebug>
 #include <QtCore/QDir>
@@ -883,6 +884,54 @@ Utils::SynchronousProcessResponse
 
     return response;
 }
+
+bool VCSBasePlugin::runPatch(const QByteArray &input, const QString &workingDirectory,
+                             int strip, bool reverse)
+{
+    VCSBaseOutputWindow *ow = VCSBaseOutputWindow::instance();
+    const QString patch = Internal::VCSPlugin::instance()->settings().patchCommand;
+    if (patch.isEmpty()) {
+        ow->appendError(tr("There is no patch-command configured in the commone 'Version Control' settings."));
+        return false;
+    }
+
+    QProcess patchProcess;
+    if (!workingDirectory.isEmpty())
+        patchProcess.setWorkingDirectory(workingDirectory);
+    QStringList args(QLatin1String("-p") + QString::number(strip));
+    if (reverse)
+        args << QLatin1String("-R");
+    ow->appendCommand(QString(), patch, args);
+    patchProcess.start(patch, args);
+    if (!patchProcess.waitForStarted()) {
+        ow->appendError(tr("Unable to launch '%1': %2").arg(patch, patchProcess.errorString()));
+        return false;
+    }
+    patchProcess.write(input);
+    patchProcess.closeWriteChannel();
+    QByteArray stdOut;
+    QByteArray stdErr;
+    if (!Utils::SynchronousProcess::readDataFromProcess(patchProcess, 30000, &stdOut, &stdErr, true)) {
+        Utils::SynchronousProcess::stopProcess(patchProcess);
+        ow->appendError(tr("A timeout occurred running '%1'").arg(patch));
+        return false;
+
+    }
+    if (!stdOut.isEmpty())
+        ow->append(QString::fromLocal8Bit(stdOut));
+    if (!stdErr.isEmpty())
+        ow->append(QString::fromLocal8Bit(stdErr));
+
+    if (patchProcess.exitStatus() != QProcess::NormalExit) {
+        ow->appendError(tr("'%1' crashed.").arg(patch));
+        return false;
+    }
+    if (patchProcess.exitCode() != 0) {
+        ow->appendError(tr("'%1' failed (exit code %2).").arg(patchProcess.exitCode()));
+        return false;
+    }
+    return true;
+}
 } // namespace VCSBase
 
 #include "vcsbaseplugin.moc"
diff --git a/src/plugins/vcsbase/vcsbaseplugin.h b/src/plugins/vcsbase/vcsbaseplugin.h
index 8a396763e0f..f4fd165ace3 100644
--- a/src/plugins/vcsbase/vcsbaseplugin.h
+++ b/src/plugins/vcsbase/vcsbaseplugin.h
@@ -218,6 +218,10 @@ public:
                    unsigned flags = 0,
                    QTextCodec *outputCodec = 0);
 
+    // Utility to run the 'patch' command
+    static bool runPatch(const QByteArray &input, const QString &workingDirectory = QString(),
+                         int strip = 0, bool reverse = false);
+
 public slots:
     // Convenience slot for "Delete current file" action. Prompts to
     // delete the file via VCSManager.
-- 
GitLab