summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/plugins/git/gitclient.cpp154
-rw-r--r--src/plugins/git/gitclient.h20
-rw-r--r--src/plugins/git/giteditor.cpp26
-rw-r--r--src/plugins/git/giteditor.h4
-rw-r--r--src/plugins/git/gitplugin.cpp4
5 files changed, 195 insertions, 13 deletions
diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp
index a323454575..ff152c5b1b 100644
--- a/src/plugins/git/gitclient.cpp
+++ b/src/plugins/git/gitclient.cpp
@@ -173,6 +173,8 @@ VCSBase::VCSBaseEditor
outputEditor = m_core->editorManager()->openEditorWithContents(kind, &title, m_msgWait);
outputEditor->file()->setProperty(registerDynamicProperty, dynamicPropertyValue);
rc = VCSBase::VCSBaseEditor::getVcsBaseEditor(outputEditor);
+ connect(rc, SIGNAL(annotateRevisionRequested(QString,QString,int)),
+ this, SLOT(slotBlameRevisionRequested(QString,QString,int)));
QTC_ASSERT(rc, return 0);
rc->setSource(source);
if (setSourceCodec)
@@ -258,7 +260,7 @@ void GitClient::status(const QString &workingDirectory)
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)
qDebug() << "log" << workingDirectory << fileNames;
@@ -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 sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileNames);
VCSBase::VCSBaseEditor *editor = createVCSEditor(kind, title, sourceFile, false, "logFileName", sourceFile);
+ editor->setFileLogAnnotateEnabled(enableAnnotationContextMenu);
executeGit(workingDirectory, arguments, editor);
}
@@ -297,7 +300,21 @@ void GitClient::show(const QString &source, const QString &id)
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)
qDebug() << "blame" << workingDirectory << fileName << lineNumber;
@@ -306,12 +323,14 @@ void GitClient::blame(const QString &workingDirectory, const QString &fileName,
if (m_plugin->settings().spaceIgnorantBlame)
arguments << QLatin1String("-w");
arguments << QLatin1String("--") << fileName;
-
+ if (!revision.isEmpty())
+ arguments << revision;
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);
- 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);
}
@@ -423,6 +442,131 @@ bool GitClient::synchronousCheckout(const QString &workingDirectory,
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)
{
if (Git::Constants::debug)
diff --git a/src/plugins/git/gitclient.h b/src/plugins/git/gitclient.h
index ef53fd3ff8..c086e88a24 100644
--- a/src/plugins/git/gitclient.h
+++ b/src/plugins/git/gitclient.h
@@ -79,8 +79,10 @@ public:
const QStringList &unstagedFileNames, const QStringList &stagedFileNames= QStringList());
void status(const QString &workingDirectory);
- void log(const QString &workingDirectory, const QStringList &fileNames);
- void blame(const QString &workingDirectory, const QString &fileName, int lineNumber = -1);
+ void log(const QString &workingDirectory, const QStringList &fileNames,
+ 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 checkout(const QString &workingDirectory, const QString &file);
void checkoutBranch(const QString &workingDirectory, const QString &branch);
@@ -95,6 +97,17 @@ public:
QString *output, QString *errorMessage);
bool synchronousShow(const QString &workingDirectory, const QString &id,
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 push(const QString &workingDirectory);
@@ -145,6 +158,9 @@ public:
public slots:
void show(const QString &source, const QString &id);
+private slots:
+ void slotBlameRevisionRequested(const QString &source, QString change, int lineNumber);
+
private:
VCSBase::VCSBaseEditor *createVCSEditor(const QString &kind,
QString title,
diff --git a/src/plugins/git/giteditor.cpp b/src/plugins/git/giteditor.cpp
index 6dd67f86d9..dd124773ae 100644
--- a/src/plugins/git/giteditor.cpp
+++ b/src/plugins/git/giteditor.cpp
@@ -32,13 +32,14 @@
#include "annotationhighlighter.h"
#include "gitconstants.h"
#include "gitplugin.h"
+#include "gitclient.h"
#include "gitsettings.h"
#include <QtCore/QTextCodec>
#include <coreplugin/editormanager/editormanager.h>
#include <utils/qtcassert.h>
#include <vcsbase/diffhighlighter.h>
-
+#include <vcsbase/vcsbaseoutputwindow.h>
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
@@ -64,6 +65,7 @@ GitEditor::GitEditor(const VCSBase::VCSBaseEditorParameters *type,
{
QTC_ASSERT(m_changeNumberPattern8.isValid(), return);
QTC_ASSERT(m_changeNumberPattern40.isValid(), return);
+ setAnnotateRevisionTextFormat(tr("Blame %1"));
if (Git::Constants::debug)
qDebug() << "GitEditor::GitEditor" << type->type << type->kind;
}
@@ -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 Git
diff --git a/src/plugins/git/giteditor.h b/src/plugins/git/giteditor.h
index 29e9e75aa1..02e0b24434 100644
--- a/src/plugins/git/giteditor.h
+++ b/src/plugins/git/giteditor.h
@@ -41,8 +41,6 @@ QT_END_NAMESPACE
namespace Git {
namespace Internal {
-class GitPlugin;
-
class GitEditor : public VCSBase::VCSBaseEditor
{
Q_OBJECT
@@ -62,10 +60,10 @@ private:
virtual VCSBase::DiffHighlighter *createDiffHighlighter() const;
virtual VCSBase::BaseAnnotationHighlighter *createAnnotationHighlighter(const QSet<QString> &changes) const;
virtual QString fileNameFromDiffSpecification(const QTextBlock &diffFileName) const;
+ virtual QStringList annotationPreviousVersions(const QString &revision) const;
const QRegExp m_changeNumberPattern8;
const QRegExp m_changeNumberPattern40;
- GitPlugin *m_plugin;
};
} // namespace Git
diff --git a/src/plugins/git/gitplugin.cpp b/src/plugins/git/gitplugin.cpp
index 30d44c7a73..eb1d835026 100644
--- a/src/plugins/git/gitplugin.cpp
+++ b/src/plugins/git/gitplugin.cpp
@@ -416,7 +416,7 @@ void GitPlugin::logFile()
{
const VCSBase::VCSBasePluginState state = currentState();
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()
@@ -424,7 +424,7 @@ void GitPlugin::blameFile()
const VCSBase::VCSBasePluginState state = currentState();
QTC_ASSERT(state.hasFile(), return)
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()