Commit 491a2716 authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

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: default avatarhunger <tobias.hunger@nokia.com>
parent fbe9925d
......@@ -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;
......
......@@ -306,6 +306,8 @@ public:
const QStringList &args);
virtual QStringList arguments() const = 0;
public slots:
virtual void redoCommand() = 0;
protected slots:
......
......@@ -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
......
......@@ -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>
......
......@@ -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;
}
......
......@@ -58,6 +58,8 @@ struct CommonVcsSettings
// Executable run to graphically prompt for a SSH-password.
QString sshPasswordPrompt;
QString patchCommand;
bool lineWrap;
int lineWrapWidth;
......
......@@ -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"
......@@ -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
......@@ -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"
......@@ -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.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment