Commit bcba9ea6 authored by Friedemann Kleint's avatar Friedemann Kleint

VCS [git]: Annotate previous version/single filelog annotation.

Task-number: QTCREATORBUG-503
parent 322ef6b8
...@@ -173,6 +173,8 @@ VCSBase::VCSBaseEditor ...@@ -173,6 +173,8 @@ VCSBase::VCSBaseEditor
outputEditor = m_core->editorManager()->openEditorWithContents(kind, &title, m_msgWait); outputEditor = m_core->editorManager()->openEditorWithContents(kind, &title, m_msgWait);
outputEditor->file()->setProperty(registerDynamicProperty, dynamicPropertyValue); outputEditor->file()->setProperty(registerDynamicProperty, dynamicPropertyValue);
rc = VCSBase::VCSBaseEditor::getVcsBaseEditor(outputEditor); rc = VCSBase::VCSBaseEditor::getVcsBaseEditor(outputEditor);
connect(rc, SIGNAL(annotateRevisionRequested(QString,QString,int)),
this, SLOT(slotBlameRevisionRequested(QString,QString,int)));
QTC_ASSERT(rc, return 0); QTC_ASSERT(rc, return 0);
rc->setSource(source); rc->setSource(source);
if (setSourceCodec) if (setSourceCodec)
...@@ -258,7 +260,7 @@ void GitClient::status(const QString &workingDirectory) ...@@ -258,7 +260,7 @@ void GitClient::status(const QString &workingDirectory)
Qt::QueuedConnection); Qt::QueuedConnection);
} }
void GitClient::log(const QString &workingDirectory, const QStringList &fileNames) void GitClient::log(const QString &workingDirectory, const QStringList &fileNames, bool enableAnnotationContextMenu)
{ {
if (Git::Constants::debug) if (Git::Constants::debug)
qDebug() << "log" << workingDirectory << fileNames; qDebug() << "log" << workingDirectory << fileNames;
...@@ -278,6 +280,7 @@ void GitClient::log(const QString &workingDirectory, const QStringList &fileName ...@@ -278,6 +280,7 @@ void GitClient::log(const QString &workingDirectory, const QStringList &fileName
const QString kind = QLatin1String(Git::Constants::GIT_LOG_EDITOR_KIND); const QString kind = QLatin1String(Git::Constants::GIT_LOG_EDITOR_KIND);
const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileNames); const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileNames);
VCSBase::VCSBaseEditor *editor = createVCSEditor(kind, title, sourceFile, false, "logFileName", sourceFile); VCSBase::VCSBaseEditor *editor = createVCSEditor(kind, title, sourceFile, false, "logFileName", sourceFile);
editor->setFileLogAnnotateEnabled(enableAnnotationContextMenu);
executeGit(workingDirectory, arguments, editor); executeGit(workingDirectory, arguments, editor);
} }
...@@ -297,7 +300,21 @@ void GitClient::show(const QString &source, const QString &id) ...@@ -297,7 +300,21 @@ void GitClient::show(const QString &source, const QString &id)
executeGit(workDir, arguments, editor); executeGit(workDir, arguments, editor);
} }
void GitClient::blame(const QString &workingDirectory, const QString &fileName, int lineNumber /* = -1 */) void GitClient::slotBlameRevisionRequested(const QString &source, QString change, int lineNumber)
{
// This might be invoked with a verbose revision description
// "SHA1 author subject" from the annotation context menu. Strip the rest.
const int blankPos = change.indexOf(QLatin1Char(' '));
if (blankPos != -1)
change.truncate(blankPos);
const QFileInfo fi(source);
blame(fi.absolutePath(), fi.fileName(), change, lineNumber);
}
void GitClient::blame(const QString &workingDirectory,
const QString &fileName,
const QString &revision /* = QString() */,
int lineNumber /* = -1 */)
{ {
if (Git::Constants::debug) if (Git::Constants::debug)
qDebug() << "blame" << workingDirectory << fileName << lineNumber; qDebug() << "blame" << workingDirectory << fileName << lineNumber;
...@@ -306,12 +323,14 @@ void GitClient::blame(const QString &workingDirectory, const QString &fileName, ...@@ -306,12 +323,14 @@ void GitClient::blame(const QString &workingDirectory, const QString &fileName,
if (m_plugin->settings().spaceIgnorantBlame) if (m_plugin->settings().spaceIgnorantBlame)
arguments << QLatin1String("-w"); arguments << QLatin1String("-w");
arguments << QLatin1String("--") << fileName; arguments << QLatin1String("--") << fileName;
if (!revision.isEmpty())
arguments << revision;
const QString kind = QLatin1String(Git::Constants::GIT_BLAME_EDITOR_KIND); const QString kind = QLatin1String(Git::Constants::GIT_BLAME_EDITOR_KIND);
const QString title = tr("Git Blame %1").arg(fileName); const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDirectory, QStringList(fileName), revision);
const QString title = tr("Git Blame %1").arg(id);
const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileName); const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileName);
VCSBase::VCSBaseEditor *editor = createVCSEditor(kind, title, sourceFile, true, "blameFileName", sourceFile); VCSBase::VCSBaseEditor *editor = createVCSEditor(kind, title, sourceFile, true, "blameFileName", id);
executeGit(workingDirectory, arguments, editor, false, GitCommand::NoReport, lineNumber); executeGit(workingDirectory, arguments, editor, false, GitCommand::NoReport, lineNumber);
} }
...@@ -423,6 +442,131 @@ bool GitClient::synchronousCheckout(const QString &workingDirectory, ...@@ -423,6 +442,131 @@ bool GitClient::synchronousCheckout(const QString &workingDirectory,
return true; return true;
} }
static inline QString msgParentRevisionFailed(const QString &workingDirectory,
const QString &revision,
const QString &why)
{
return GitClient::tr("Unable to find parent revisions of %1 in %2: %3").arg(revision, workingDirectory, why);
}
static inline QString msgInvalidRevision()
{
return GitClient::tr("Invalid revision");
}
// Split a line of "<commit> <parent1> ..." to obtain parents from "rev-list" or "log".
static inline bool splitCommitParents(const QString &line,
QString *commit = 0,
QStringList *parents = 0)
{
if (commit)
commit->clear();
if (parents)
parents->clear();
QStringList tokens = line.trimmed().split(QLatin1Char(' '));
if (tokens.size() < 2)
return false;
if (commit)
*commit = tokens.front();
tokens.pop_front();
if (parents)
*parents = tokens;
return true;
}
// Find out the immediate parent revisions of a revision of the repository.
// Might be several in case of merges.
bool GitClient::synchronousParentRevisions(const QString &workingDirectory,
const QStringList &files /* = QStringList() */,
const QString &revision,
QStringList *parents,
QString *errorMessage)
{
if (Git::Constants::debug)
qDebug() << Q_FUNC_INFO << workingDirectory << revision;
QByteArray outputTextData;
QByteArray errorText;
QStringList arguments;
arguments << QLatin1String("rev-list") << QLatin1String(GitClient::noColorOption)
<< QLatin1String("--parents") << QLatin1String("--max-count=1") << revision;
if (!files.isEmpty()) {
arguments.append(QLatin1String("--"));
arguments.append(files);
}
const bool rc = synchronousGit(workingDirectory, arguments, &outputTextData, &errorText);
if (!rc) {
*errorMessage = msgParentRevisionFailed(workingDirectory, revision, QString::fromLocal8Bit(errorText));
return false;
}
// Should result in one line of blank-delimited revisions, specifying current first
// unless it is top.
QString outputText = QString::fromLocal8Bit(outputTextData);
outputText.remove(QLatin1Char('\r'));
outputText.remove(QLatin1Char('\n'));
if (!splitCommitParents(outputText, 0, parents)) {
*errorMessage = msgParentRevisionFailed(workingDirectory, revision, msgInvalidRevision());
return false;
}
if (Git::Constants::debug)
qDebug() << workingDirectory << files << revision << "->" << *parents;
return true;
}
// Short SHA1, author, subject
static const char defaultShortLogFormatC[] = "%h (%an \"%s\")";
bool GitClient::synchronousShortDescription(const QString &workingDirectory, const QString &revision,
QString *description, QString *errorMessage)
{
// Short SHA 1, author, subject
return synchronousShortDescription(workingDirectory, revision,
QLatin1String(defaultShortLogFormatC),
description, errorMessage);
}
// Convenience working on a list of revisions
bool GitClient::synchronousShortDescriptions(const QString &workingDirectory, const QStringList &revisions,
QStringList *descriptions, QString *errorMessage)
{
descriptions->clear();
foreach (const QString &revision, revisions) {
QString description;
if (!synchronousShortDescription(workingDirectory, revision, &description, errorMessage)) {
descriptions->clear();
return false;
}
descriptions->push_back(description);
}
return true;
}
// Format an entry in a one-liner for selection list using git log.
bool GitClient::synchronousShortDescription(const QString &workingDirectory,
const QString &revision,
const QString &format,
QString *description,
QString *errorMessage)
{
if (Git::Constants::debug)
qDebug() << Q_FUNC_INFO << workingDirectory << revision;
QByteArray outputTextData;
QByteArray errorText;
QStringList arguments;
arguments << QLatin1String("log") << QLatin1String(GitClient::noColorOption)
<< (QLatin1String("--pretty=format:") + format)
<< QLatin1String("--max-count=1") << revision;
const bool rc = synchronousGit(workingDirectory, arguments, &outputTextData, &errorText);
if (!rc) {
*errorMessage = tr("Unable to describe revision %1 in %2: %3").arg(revision, workingDirectory, QString::fromLocal8Bit(errorText));
return false;
}
*description = QString::fromLocal8Bit(outputTextData);
description->remove(QLatin1Char('\r'));
if (description->endsWith(QLatin1Char('\n')))
description->truncate(description->size() - 1);
return true;
}
bool GitClient::synchronousStash(const QString &workingDirectory, QString *errorMessage) bool GitClient::synchronousStash(const QString &workingDirectory, QString *errorMessage)
{ {
if (Git::Constants::debug) if (Git::Constants::debug)
......
...@@ -79,8 +79,10 @@ public: ...@@ -79,8 +79,10 @@ public:
const QStringList &unstagedFileNames, const QStringList &stagedFileNames= QStringList()); const QStringList &unstagedFileNames, const QStringList &stagedFileNames= QStringList());
void status(const QString &workingDirectory); void status(const QString &workingDirectory);
void log(const QString &workingDirectory, const QStringList &fileNames); void log(const QString &workingDirectory, const QStringList &fileNames,
void blame(const QString &workingDirectory, const QString &fileName, int lineNumber = -1); bool enableAnnotationContextMenu = false);
void blame(const QString &workingDirectory, const QString &fileName,
const QString &revision = QString(), int lineNumber = -1);
void showCommit(const QString &workingDirectory, const QString &commit); void showCommit(const QString &workingDirectory, const QString &commit);
void checkout(const QString &workingDirectory, const QString &file); void checkout(const QString &workingDirectory, const QString &file);
void checkoutBranch(const QString &workingDirectory, const QString &branch); void checkoutBranch(const QString &workingDirectory, const QString &branch);
...@@ -95,6 +97,17 @@ public: ...@@ -95,6 +97,17 @@ public:
QString *output, QString *errorMessage); QString *output, QString *errorMessage);
bool synchronousShow(const QString &workingDirectory, const QString &id, bool synchronousShow(const QString &workingDirectory, const QString &id,
QString *output, QString *errorMessage); QString *output, QString *errorMessage);
bool synchronousParentRevisions(const QString &workingDirectory,
const QStringList &files /* = QStringList() */,
const QString &revision,
QStringList *parents,
QString *errorMessage);
bool synchronousShortDescription(const QString &workingDirectory, const QString &revision,
QString *description, QString *errorMessage);
bool synchronousShortDescription(const QString &workingDirectory, const QString &revision,
const QString &format, QString *description, QString *errorMessage);
bool synchronousShortDescriptions(const QString &workingDirectory, const QStringList &revisions,
QStringList *descriptions, QString *errorMessage);
void pull(const QString &workingDirectory); void pull(const QString &workingDirectory);
void push(const QString &workingDirectory); void push(const QString &workingDirectory);
...@@ -145,6 +158,9 @@ public: ...@@ -145,6 +158,9 @@ public:
public slots: public slots:
void show(const QString &source, const QString &id); void show(const QString &source, const QString &id);
private slots:
void slotBlameRevisionRequested(const QString &source, QString change, int lineNumber);
private: private:
VCSBase::VCSBaseEditor *createVCSEditor(const QString &kind, VCSBase::VCSBaseEditor *createVCSEditor(const QString &kind,
QString title, QString title,
......
...@@ -32,13 +32,14 @@ ...@@ -32,13 +32,14 @@
#include "annotationhighlighter.h" #include "annotationhighlighter.h"
#include "gitconstants.h" #include "gitconstants.h"
#include "gitplugin.h" #include "gitplugin.h"
#include "gitclient.h"
#include "gitsettings.h" #include "gitsettings.h"
#include <QtCore/QTextCodec> #include <QtCore/QTextCodec>
#include <coreplugin/editormanager/editormanager.h> #include <coreplugin/editormanager/editormanager.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <vcsbase/diffhighlighter.h> #include <vcsbase/diffhighlighter.h>
#include <vcsbase/vcsbaseoutputwindow.h>
#include <QtCore/QDebug> #include <QtCore/QDebug>
#include <QtCore/QDir> #include <QtCore/QDir>
#include <QtCore/QFileInfo> #include <QtCore/QFileInfo>
...@@ -64,6 +65,7 @@ GitEditor::GitEditor(const VCSBase::VCSBaseEditorParameters *type, ...@@ -64,6 +65,7 @@ GitEditor::GitEditor(const VCSBase::VCSBaseEditorParameters *type,
{ {
QTC_ASSERT(m_changeNumberPattern8.isValid(), return); QTC_ASSERT(m_changeNumberPattern8.isValid(), return);
QTC_ASSERT(m_changeNumberPattern40.isValid(), return); QTC_ASSERT(m_changeNumberPattern40.isValid(), return);
setAnnotateRevisionTextFormat(tr("Blame %1"));
if (Git::Constants::debug) if (Git::Constants::debug)
qDebug() << "GitEditor::GitEditor" << type->type << type->kind; qDebug() << "GitEditor::GitEditor" << type->type << type->kind;
} }
...@@ -186,6 +188,28 @@ void GitEditor::commandFinishedGotoLine(bool ok, const QVariant &v) ...@@ -186,6 +188,28 @@ void GitEditor::commandFinishedGotoLine(bool ok, const QVariant &v)
} }
} }
QStringList GitEditor::annotationPreviousVersions(const QString &revision) const
{
QStringList revisions;
QString errorMessage;
GitClient *client = GitPlugin::instance()->gitClient();
const QFileInfo fi(source());
const QString workingDirectory = fi.absolutePath();
// Get the SHA1's of the file.
if (!client->synchronousParentRevisions(workingDirectory, QStringList(fi.fileName()),
revision, &revisions, &errorMessage)) {
VCSBase::VCSBaseOutputWindow::instance()->appendSilently(errorMessage);
return QStringList();
}
// Format verbose, SHA1 being first token
QStringList descriptions;
if (!client->synchronousShortDescriptions(workingDirectory, revisions, &descriptions, &errorMessage)) {
VCSBase::VCSBaseOutputWindow::instance()->appendSilently(errorMessage);
return QStringList();
}
return descriptions;
}
} // namespace Internal } // namespace Internal
} // namespace Git } // namespace Git
...@@ -41,8 +41,6 @@ QT_END_NAMESPACE ...@@ -41,8 +41,6 @@ QT_END_NAMESPACE
namespace Git { namespace Git {
namespace Internal { namespace Internal {
class GitPlugin;
class GitEditor : public VCSBase::VCSBaseEditor class GitEditor : public VCSBase::VCSBaseEditor
{ {
Q_OBJECT Q_OBJECT
...@@ -62,10 +60,10 @@ private: ...@@ -62,10 +60,10 @@ private:
virtual VCSBase::DiffHighlighter *createDiffHighlighter() const; virtual VCSBase::DiffHighlighter *createDiffHighlighter() const;
virtual VCSBase::BaseAnnotationHighlighter *createAnnotationHighlighter(const QSet<QString> &changes) const; virtual VCSBase::BaseAnnotationHighlighter *createAnnotationHighlighter(const QSet<QString> &changes) const;
virtual QString fileNameFromDiffSpecification(const QTextBlock &diffFileName) const; virtual QString fileNameFromDiffSpecification(const QTextBlock &diffFileName) const;
virtual QStringList annotationPreviousVersions(const QString &revision) const;
const QRegExp m_changeNumberPattern8; const QRegExp m_changeNumberPattern8;
const QRegExp m_changeNumberPattern40; const QRegExp m_changeNumberPattern40;
GitPlugin *m_plugin;
}; };
} // namespace Git } // namespace Git
......
...@@ -416,7 +416,7 @@ void GitPlugin::logFile() ...@@ -416,7 +416,7 @@ void GitPlugin::logFile()
{ {
const VCSBase::VCSBasePluginState state = currentState(); const VCSBase::VCSBasePluginState state = currentState();
QTC_ASSERT(state.hasFile(), return) QTC_ASSERT(state.hasFile(), return)
m_gitClient->log(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile())); m_gitClient->log(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()), true);
} }
void GitPlugin::blameFile() void GitPlugin::blameFile()
...@@ -424,7 +424,7 @@ void GitPlugin::blameFile() ...@@ -424,7 +424,7 @@ void GitPlugin::blameFile()
const VCSBase::VCSBasePluginState state = currentState(); const VCSBase::VCSBasePluginState state = currentState();
QTC_ASSERT(state.hasFile(), return) QTC_ASSERT(state.hasFile(), return)
const int lineNumber = VCSBase::VCSBaseEditor::lineNumberOfCurrentEditor(state.currentFile()); const int lineNumber = VCSBase::VCSBaseEditor::lineNumberOfCurrentEditor(state.currentFile());
m_gitClient->blame(state.currentFileTopLevel(), state.relativeCurrentFile(), lineNumber); m_gitClient->blame(state.currentFileTopLevel(), state.relativeCurrentFile(), QString(), lineNumber);
} }
void GitPlugin::logProject() void GitPlugin::logProject()
......
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