From bcba9ea604e33c2d26f4180646e2875eb8766126 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint <Friedemann.Kleint@nokia.com> Date: Thu, 7 Jan 2010 17:29:14 +0100 Subject: [PATCH] VCS [git]: Annotate previous version/single filelog annotation. Task-number: QTCREATORBUG-503 --- src/plugins/git/gitclient.cpp | 154 ++++++++++++++++++++++++++++++++-- src/plugins/git/gitclient.h | 20 ++++- src/plugins/git/giteditor.cpp | 26 +++++- src/plugins/git/giteditor.h | 4 +- src/plugins/git/gitplugin.cpp | 4 +- 5 files changed, 195 insertions(+), 13 deletions(-) diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp index a323454575f..ff152c5b1b6 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 ef53fd3ff88..c086e88a24b 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 6dd67f86d9e..dd124773aef 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 29e9e75aa19..02e0b24434c 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 30d44c7a73c..eb1d8350266 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() -- GitLab