Commit 367cfc84 authored by Francois Ferrand's avatar Francois Ferrand Committed by Orgad Shaneh
Browse files

Git: Support staging a single chunk.



Add context menu in diff editor to stage/unstage a single chunk from the diff.

Task-number: QTCREATORBUG-5875
Change-Id: Ic244a0d84b5ed5f66b90d7fe8784fc1b8041d183
Reviewed-by: Orgad Shaneh's avatarOrgad Shaneh <orgads@gmail.com>
Reviewed-by: default avatarTobias Hunger <tobias.hunger@digia.com>
parent c372f7b6
...@@ -1115,6 +1115,8 @@ void GitClient::diff(const QString &workingDirectory, ...@@ -1115,6 +1115,8 @@ void GitClient::diff(const QString &workingDirectory,
workingDirectory, workingDirectory,
argWidget); argWidget);
newEditor = vcsEditor->editor(); newEditor = vcsEditor->editor();
connect(vcsEditor, SIGNAL(diffChunkApplied(VcsBase::DiffChunk)),
argWidget, SLOT(executeCommand()));
connect(vcsEditor, SIGNAL(diffChunkReverted(VcsBase::DiffChunk)), connect(vcsEditor, SIGNAL(diffChunkReverted(VcsBase::DiffChunk)),
argWidget, SLOT(executeCommand())); argWidget, SLOT(executeCommand()));
} }
...@@ -1222,6 +1224,8 @@ void GitClient::diff(const QString &workingDirectory, ...@@ -1222,6 +1224,8 @@ void GitClient::diff(const QString &workingDirectory,
sourceFile, sourceFile,
argWidget); argWidget);
newEditor = vcsEditor->editor(); newEditor = vcsEditor->editor();
connect(vcsEditor, SIGNAL(diffChunkApplied(VcsBase::DiffChunk)),
argWidget, SLOT(executeCommand()));
connect(vcsEditor, SIGNAL(diffChunkReverted(VcsBase::DiffChunk)), connect(vcsEditor, SIGNAL(diffChunkReverted(VcsBase::DiffChunk)),
argWidget, SLOT(executeCommand())); argWidget, SLOT(executeCommand()));
} }
...@@ -2433,10 +2437,11 @@ bool GitClient::synchronousCleanList(const QString &workingDirectory, QStringLis ...@@ -2433,10 +2437,11 @@ bool GitClient::synchronousCleanList(const QString &workingDirectory, QStringLis
} }
bool GitClient::synchronousApplyPatch(const QString &workingDirectory, bool GitClient::synchronousApplyPatch(const QString &workingDirectory,
const QString &file, QString *errorMessage) const QString &file, QString *errorMessage,
const QStringList &arguments)
{ {
QStringList args; QStringList args;
args << QLatin1String("apply") << QLatin1String("--whitespace=fix") << file; args << QLatin1String("apply") << QLatin1String("--whitespace=fix") << arguments << file;
QByteArray outputText; QByteArray outputText;
QByteArray errorText; QByteArray errorText;
const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText); const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText);
......
...@@ -182,7 +182,7 @@ public: ...@@ -182,7 +182,7 @@ public:
const QStringList &files = QStringList(), const QStringList &files = QStringList(),
QString *errorMessage = 0); QString *errorMessage = 0);
bool synchronousCleanList(const QString &workingDirectory, QStringList *files, QStringList *ignoredFiles, QString *errorMessage); bool synchronousCleanList(const QString &workingDirectory, QStringList *files, QStringList *ignoredFiles, QString *errorMessage);
bool synchronousApplyPatch(const QString &workingDirectory, const QString &file, QString *errorMessage); bool synchronousApplyPatch(const QString &workingDirectory, const QString &file, QString *errorMessage, const QStringList &arguments = QStringList());
bool synchronousInit(const QString &workingDirectory); bool synchronousInit(const QString &workingDirectory);
bool synchronousCheckoutFiles(const QString &workingDirectory, bool synchronousCheckoutFiles(const QString &workingDirectory,
QStringList files = QStringList(), QStringList files = QStringList(),
......
...@@ -44,9 +44,12 @@ ...@@ -44,9 +44,12 @@
#include <QFileInfo> #include <QFileInfo>
#include <QRegExp> #include <QRegExp>
#include <QSet> #include <QSet>
#include <QTemporaryFile>
#include <QDir>
#include <QTextCursor> #include <QTextCursor>
#include <QTextBlock> #include <QTextBlock>
#include <QMessageBox>
#define CHANGE_PATTERN "[a-f0-9]{7,40}" #define CHANGE_PATTERN "[a-f0-9]{7,40}"
...@@ -223,6 +226,53 @@ void GitEditor::revertChange() ...@@ -223,6 +226,53 @@ void GitEditor::revertChange()
GitPlugin::instance()->gitClient()->synchronousRevert(workingDirectory, m_currentChange); GitPlugin::instance()->gitClient()->synchronousRevert(workingDirectory, m_currentChange);
} }
void GitEditor::stageDiffChunk()
{
const QAction *a = qobject_cast<QAction *>(sender());
QTC_ASSERT(a, return);
const VcsBase::DiffChunk chunk = qvariant_cast<VcsBase::DiffChunk>(a->data());
return applyDiffChunk(chunk, false);
}
void GitEditor::unstageDiffChunk()
{
const QAction *a = qobject_cast<QAction *>(sender());
QTC_ASSERT(a, return);
const VcsBase::DiffChunk chunk = qvariant_cast<VcsBase::DiffChunk>(a->data());
return applyDiffChunk(chunk, true);
}
void GitEditor::applyDiffChunk(const VcsBase::DiffChunk& chunk, bool revert)
{
VcsBase::VcsBaseOutputWindow *outwin = VcsBase::VcsBaseOutputWindow::instance();
QTemporaryFile patchFile;
if (!patchFile.open())
return;
const QString baseDir = diffBaseDirectory();
patchFile.write(chunk.header);
patchFile.write(chunk.chunk);
patchFile.close();
GitClient *client = GitPlugin::instance()->gitClient();
QStringList args = QStringList() << QLatin1String("--cached");
if (revert)
args << QLatin1String("--reverse");
QString errorMessage;
if (client->synchronousApplyPatch(baseDir, patchFile.fileName(), &errorMessage, args)) {
if (errorMessage.isEmpty())
outwin->append(tr("Chunk successfully staged"));
else
outwin->append(errorMessage);
if (revert)
emit diffChunkReverted(chunk);
else
emit diffChunkApplied(chunk);
} else {
outwin->appendError(errorMessage);
}
}
void GitEditor::init() void GitEditor::init()
{ {
VcsBase::VcsBaseEditorWidget::init(); VcsBase::VcsBaseEditorWidget::init();
...@@ -233,6 +283,19 @@ void GitEditor::init() ...@@ -233,6 +283,19 @@ void GitEditor::init()
new GitRebaseHighlighter(baseTextDocument().data()); new GitRebaseHighlighter(baseTextDocument().data());
} }
void GitEditor::addDiffActions(QMenu *menu, const VcsBase::DiffChunk &chunk)
{
menu->addSeparator();
QAction *stageAction = menu->addAction(tr("Stage Chunk..."));
stageAction->setData(qVariantFromValue(chunk));
connect(stageAction, SIGNAL(triggered()), this, SLOT(stageDiffChunk()));
QAction *unstageAction = menu->addAction(tr("Unstage Chunk..."));
unstageAction->setData(qVariantFromValue(chunk));
connect(unstageAction, SIGNAL(triggered()), this, SLOT(unstageDiffChunk()));
}
bool GitEditor::open(QString *errorString, const QString &fileName, const QString &realFileName) bool GitEditor::open(QString *errorString, const QString &fileName, const QString &realFileName)
{ {
bool res = VcsBaseEditorWidget::open(errorString, fileName, realFileName); bool res = VcsBaseEditorWidget::open(errorString, fileName, realFileName);
......
...@@ -57,9 +57,13 @@ public slots: ...@@ -57,9 +57,13 @@ public slots:
private slots: private slots:
void cherryPickChange(); void cherryPickChange();
void revertChange(); void revertChange();
void stageDiffChunk();
void unstageDiffChunk();
void applyDiffChunk(const VcsBase::DiffChunk& chunk, bool revert);
private: private:
void init(); void init();
void addDiffActions(QMenu *menu, const VcsBase::DiffChunk &chunk);
bool open(QString *errorString, const QString &fileName, const QString &realFileName); bool open(QString *errorString, const QString &fileName, const QString &realFileName);
QSet<QString> annotationChanges() const; QSet<QString> annotationChanges() const;
QString changeUnderCursor(const QTextCursor &) const; QString changeUnderCursor(const QTextCursor &) const;
......
...@@ -950,6 +950,8 @@ void VcsBaseEditorWidget::contextMenuEvent(QContextMenuEvent *e) ...@@ -950,6 +950,8 @@ void VcsBaseEditorWidget::contextMenuEvent(QContextMenuEvent *e)
QAction *revertAction = menu->addAction(tr("Revert Chunk...")); QAction *revertAction = menu->addAction(tr("Revert Chunk..."));
revertAction->setData(qVariantFromValue(Internal::DiffChunkAction(chunk, true))); revertAction->setData(qVariantFromValue(Internal::DiffChunkAction(chunk, true)));
connect(revertAction, SIGNAL(triggered()), this, SLOT(slotApplyDiffChunk())); connect(revertAction, SIGNAL(triggered()), this, SLOT(slotApplyDiffChunk()));
// Custom diff actions
addDiffActions(menu, chunk);
break; break;
} }
default: default:
...@@ -1145,7 +1147,8 @@ DiffChunk VcsBaseEditorWidget::diffChunk(QTextCursor cursor) const ...@@ -1145,7 +1147,8 @@ DiffChunk VcsBaseEditorWidget::diffChunk(QTextCursor cursor) const
} }
if (!chunkStart || !block.isValid()) if (!chunkStart || !block.isValid())
return rc; return rc;
rc.fileName = findDiffFile(fileNameFromDiffSpecification(block)); QString header;
rc.fileName = findDiffFile(fileNameFromDiffSpecification(block, &header));
if (rc.fileName.isEmpty()) if (rc.fileName.isEmpty())
return rc; return rc;
// Concatenate chunk and convert // Concatenate chunk and convert
...@@ -1163,6 +1166,7 @@ DiffChunk VcsBaseEditorWidget::diffChunk(QTextCursor cursor) const ...@@ -1163,6 +1166,7 @@ DiffChunk VcsBaseEditorWidget::diffChunk(QTextCursor cursor) const
} }
const QTextCodec *cd = baseTextDocument()->codec(); const QTextCodec *cd = baseTextDocument()->codec();
rc.chunk = cd ? cd->fromUnicode(unicode) : unicode.toLocal8Bit(); rc.chunk = cd ? cd->fromUnicode(unicode) : unicode.toLocal8Bit();
rc.header = cd ? cd->fromUnicode(header) : header.toLocal8Bit();
return rc; return rc;
} }
...@@ -1388,6 +1392,10 @@ QString VcsBaseEditorWidget::findDiffFile(const QString &f) const ...@@ -1388,6 +1392,10 @@ QString VcsBaseEditorWidget::findDiffFile(const QString &f) const
return QString(); return QString();
} }
void VcsBaseEditorWidget::addDiffActions(QMenu *, const DiffChunk &)
{
}
void VcsBaseEditorWidget::slotAnnotateRevision() void VcsBaseEditorWidget::slotAnnotateRevision()
{ {
if (const QAction *a = qobject_cast<const QAction *>(sender())) if (const QAction *a = qobject_cast<const QAction *>(sender()))
...@@ -1430,18 +1438,25 @@ bool VcsBaseEditorWidget::applyDiffChunk(const DiffChunk &dc, bool revert) const ...@@ -1430,18 +1438,25 @@ bool VcsBaseEditorWidget::applyDiffChunk(const DiffChunk &dc, bool revert) const
d->m_diffBaseDirectory, 0, revert); d->m_diffBaseDirectory, 0, revert);
} }
QString VcsBaseEditorWidget::fileNameFromDiffSpecification(const QTextBlock &inBlock) const QString VcsBaseEditorWidget::fileNameFromDiffSpecification(const QTextBlock &inBlock, QString *header) const
{ {
// Go back chunks // Go back chunks
QString fileName;
for (QTextBlock block = inBlock; block.isValid(); block = block.previous()) { for (QTextBlock block = inBlock; block.isValid(); block = block.previous()) {
const QString line = block.text(); const QString line = block.text();
if (d->m_diffFilePattern.indexIn(line) != -1) { if (d->m_diffFilePattern.indexIn(line) != -1) {
QString cap = d->m_diffFilePattern.cap(1); QString cap = d->m_diffFilePattern.cap(1);
if (!cap.isEmpty()) if (header)
return findDiffFile(cap); header->prepend(line + QLatin1String("\n"));
if (fileName.isEmpty() && !cap.isEmpty())
fileName = cap;
} else if (!fileName.isEmpty()) {
return findDiffFile(fileName);
} else if (header) {
header->clear();
} }
} }
return QString(); return fileName.isEmpty() ? QString() : findDiffFile(fileName);
} }
void VcsBaseEditorWidget::addChangeActions(QMenu *, const QString &) void VcsBaseEditorWidget::addChangeActions(QMenu *, const QString &)
......
...@@ -83,6 +83,7 @@ public: ...@@ -83,6 +83,7 @@ public:
QString fileName; QString fileName;
QByteArray chunk; QByteArray chunk;
QByteArray header;
}; };
class VCSBASE_EXPORT VcsBaseEditorWidget : public TextEditor::BaseTextEditorWidget class VCSBASE_EXPORT VcsBaseEditorWidget : public TextEditor::BaseTextEditorWidget
...@@ -234,6 +235,8 @@ protected: ...@@ -234,6 +235,8 @@ protected:
* source and version control. */ * source and version control. */
virtual QString findDiffFile(const QString &f) const; virtual QString findDiffFile(const QString &f) const;
virtual void addDiffActions(QMenu *menu, const DiffChunk &chunk);
virtual void addChangeActions(QMenu *menu, const QString &change); virtual void addChangeActions(QMenu *menu, const QString &change);
// Implement to return a set of change identifiers in // Implement to return a set of change identifiers in
...@@ -245,7 +248,7 @@ protected: ...@@ -245,7 +248,7 @@ protected:
virtual BaseAnnotationHighlighter *createAnnotationHighlighter(const QSet<QString> &changes) const = 0; virtual BaseAnnotationHighlighter *createAnnotationHighlighter(const QSet<QString> &changes) const = 0;
// Returns a local file name from the diff file specification // Returns a local file name from the diff file specification
// (text cursor at position above change hunk) // (text cursor at position above change hunk)
QString fileNameFromDiffSpecification(const QTextBlock &inBlock) const; QString fileNameFromDiffSpecification(const QTextBlock &inBlock, QString *header = 0) const;
// Implement to return decorated annotation change for "Annotate version" // Implement to return decorated annotation change for "Annotate version"
virtual QString decorateVersion(const QString &revision) const; virtual QString decorateVersion(const QString &revision) const;
......
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