Add support for annotation of any given revision

- Parent commits are also accessible from Annotate context menu
- The client functionality was added in `fossil v2.4`

Change-Id: Ia6096432cb1151388b5aebca30a6d25c1c6079f4
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
This commit is contained in:
Artur Shepilko
2018-07-16 12:25:41 -05:00
parent b1e1cdef86
commit 88e11284ea
7 changed files with 111 additions and 25 deletions

View File

@@ -99,7 +99,7 @@ public:
// This way the annotated line number would not get offset by the version list. // This way the annotated line number would not get offset by the version list.
settings.setValue(FossilSettings::annotateListVersionsKey, false); settings.setValue(FossilSettings::annotateListVersionsKey, false);
mapSetting(addToggleButton(QLatin1String("--log"), tr("List Versions")), mapSetting(addToggleButton("--log", tr("List Versions")),
settings.boolPointer(FossilSettings::annotateListVersionsKey)); settings.boolPointer(FossilSettings::annotateListVersionsKey));
} }
}; };
@@ -342,7 +342,23 @@ QList<BranchInfo> FossilClient::synchronousBranchQuery(const QString &workingDir
return branches; return branches;
} }
RevisionInfo FossilClient::synchronousRevisionQuery(const QString &workingDirectory, const QString &id) QStringList FossilClient::parseRevisionCommentLine(const QString &commentLine)
{
// "comment: This is a (test) commit message (user: the.name)"
const QRegularExpression commentRx("^comment:\\s+(.*)\\s\\(user:\\s(.*)\\)$",
QRegularExpression::CaseInsensitiveOption);
QTC_ASSERT(commentRx.isValid(), return QStringList());
const QRegularExpressionMatch match = commentRx.match(commentLine);
if (!match.hasMatch())
return QStringList();
return QStringList({match.captured(1), match.captured(2)});
}
RevisionInfo FossilClient::synchronousRevisionQuery(const QString &workingDirectory, const QString &id,
bool getCommentMsg) const
{ {
// Query details of the given revision/check-out id, // Query details of the given revision/check-out id,
// if none specified, provide information about current revision // if none specified, provide information about current revision
@@ -361,6 +377,9 @@ RevisionInfo FossilClient::synchronousRevisionQuery(const QString &workingDirect
QString revisionId; QString revisionId;
QString parentId; QString parentId;
QStringList mergeParentIds;
QString commentMsg;
QString committer;
const QRegularExpression idRx("([0-9a-f]{5,40})"); const QRegularExpression idRx("([0-9a-f]{5,40})");
QTC_ASSERT(idRx.isValid(), return RevisionInfo()); QTC_ASSERT(idRx.isValid(), return RevisionInfo());
@@ -376,6 +395,15 @@ RevisionInfo FossilClient::synchronousRevisionQuery(const QString &workingDirect
const QRegularExpressionMatch idMatch = idRx.match(l); const QRegularExpressionMatch idMatch = idRx.match(l);
if (idMatch.hasMatch()) if (idMatch.hasMatch())
parentId = idMatch.captured(1); parentId = idMatch.captured(1);
} else if (l.startsWith("merged-from: ", Qt::CaseInsensitive)) {
const QRegularExpressionMatch idMatch = idRx.match(l);
if (idMatch.hasMatch())
mergeParentIds.append(idMatch.captured(1));
} else if (getCommentMsg
&& l.startsWith("comment: ", Qt::CaseInsensitive)) {
const QStringList commentLineParts = parseRevisionCommentLine(l);
commentMsg = commentLineParts.value(0);
committer = commentLineParts.value(1);
} }
} }
@@ -385,7 +413,7 @@ RevisionInfo FossilClient::synchronousRevisionQuery(const QString &workingDirect
if (parentId.isEmpty()) if (parentId.isEmpty())
parentId = revisionId; // root parentId = revisionId; // root
return RevisionInfo(revisionId, parentId); return RevisionInfo(revisionId, parentId, mergeParentIds, commentMsg, committer);
} }
QStringList FossilClient::synchronousTagQuery(const QString &workingDirectory, const QString &id) QStringList FossilClient::synchronousTagQuery(const QString &workingDirectory, const QString &id)
@@ -452,8 +480,7 @@ RepositorySettings FossilClient::synchronousSettingsQuery(const QString &working
|| lcValue == "2") || lcValue == "2")
repoSettings.autosync = RepositorySettings::AutosyncPullOnly; repoSettings.autosync = RepositorySettings::AutosyncPullOnly;
} }
else if (property == "ssl-identity") {
if (property == "ssl-identity") {
repoSettings.sslIdentityFile = value; repoSettings.sslIdentityFile = value;
} }
} }
@@ -582,6 +609,7 @@ QString FossilClient::synchronousTopic(const QString &workingDirectory)
return QString(); return QString();
// return current branch name // return current branch name
const BranchInfo branchInfo = synchronousCurrentBranch(workingDirectory); const BranchInfo branchInfo = synchronousCurrentBranch(workingDirectory);
if (branchInfo.name().isEmpty()) if (branchInfo.name().isEmpty())
return QString(); return QString();
@@ -726,7 +754,7 @@ VcsBase::VcsBaseEditorWidget *FossilClient::annotate(
QString vcsCmdString = vcsCommandString(AnnotateCommand); QString vcsCmdString = vcsCommandString(AnnotateCommand);
const Core::Id kind = vcsEditorKind(AnnotateCommand); const Core::Id kind = vcsEditorKind(AnnotateCommand);
const QString id = VcsBase::VcsBaseEditor::getSource(workingDir, QStringList(file)); const QString id = VcsBase::VcsBaseEditor::getTitleId(workingDir, QStringList(file), revision);
const QString title = vcsEditorTitle(vcsCmdString, id); const QString title = vcsEditorTitle(vcsCmdString, id);
const QString source = VcsBase::VcsBaseEditor::getSource(workingDir, file); const QString source = VcsBase::VcsBaseEditor::getSource(workingDir, file);
@@ -734,13 +762,6 @@ VcsBase::VcsBaseEditorWidget *FossilClient::annotate(
VcsBase::VcsBaseEditor::getCodec(source), VcsBase::VcsBaseEditor::getCodec(source),
vcsCmdString.toLatin1().constData(), id); vcsCmdString.toLatin1().constData(), id);
// We need to be able to re-query the configuration widget for the arguments
// each time the Annotate is requested from the main menu. This allows processing of
// the effective args controlled via configuration widget.
// However VcsBaseEditorWidget no longer stores the configuration widget and thus
// does not support configurationWidget() query.
// So we re-implement the configurationWidget() in FossilEditorWidget sub-class.
auto *fossilEditor = qobject_cast<FossilEditorWidget *>(editor); auto *fossilEditor = qobject_cast<FossilEditorWidget *>(editor);
QTC_ASSERT(fossilEditor, return editor); QTC_ASSERT(fossilEditor, return editor);
@@ -769,7 +790,11 @@ VcsBase::VcsBaseEditorWidget *FossilClient::annotate(
effectiveArgs.removeAt(pos); effectiveArgs.removeAt(pos);
} }
QStringList args(vcsCmdString); QStringList args(vcsCmdString);
args << revisionSpec(revision) << effectiveArgs << file; if (!revision.isEmpty()
&& supportedFeatures().testFlag(AnnotateRevisionFeature))
args << "-r" << revision;
args << effectiveArgs << file;
// When version list requested, ignore the source line. // When version list requested, ignore the source line.
if (args.contains("--log")) if (args.contains("--log"))
@@ -805,7 +830,7 @@ bool FossilClient::managesFile(const QString &workingDirectory, const QString &f
if (response.result != Utils::SynchronousProcessResponse::Finished) if (response.result != Utils::SynchronousProcessResponse::Finished)
return false; return false;
QString output = sanitizeFossilOutput(response.stdOut()); QString output = sanitizeFossilOutput(response.stdOut());
return !output.startsWith("no history for file"); return !output.startsWith("no history for file", Qt::CaseInsensitive);
} }
unsigned int FossilClient::binaryVersion() const unsigned int FossilClient::binaryVersion() const
@@ -851,7 +876,9 @@ FossilClient::SupportedFeatures FossilClient::supportedFeatures() const
const unsigned int version = binaryVersion(); const unsigned int version = binaryVersion();
if (version < 0x13000) { if (version < 0x20400) {
features &= ~AnnotateRevisionFeature;
if (version < 0x13000)
features &= ~TimelinePathFeature; features &= ~TimelinePathFeature;
if (version < 0x12900) if (version < 0x12900)
features &= ~DiffIgnoreWhiteSpaceFeature; features &= ~DiffIgnoreWhiteSpaceFeature;
@@ -860,6 +887,7 @@ FossilClient::SupportedFeatures FossilClient::supportedFeatures() const
features &= ~TimelineWidthFeature; features &= ~TimelineWidthFeature;
} }
} }
return features; return features;
} }

View File

@@ -48,11 +48,13 @@ public:
TimelineWidthFeature = 0x4, TimelineWidthFeature = 0x4,
DiffIgnoreWhiteSpaceFeature = 0x8, DiffIgnoreWhiteSpaceFeature = 0x8,
TimelinePathFeature = 0x10, TimelinePathFeature = 0x10,
AnnotateRevisionFeature = 0x20,
AllSupportedFeatures = // | all defined features AllSupportedFeatures = // | all defined features
AnnotateBlameFeature AnnotateBlameFeature
| TimelineWidthFeature | TimelineWidthFeature
| DiffIgnoreWhiteSpaceFeature | DiffIgnoreWhiteSpaceFeature
| TimelinePathFeature | TimelinePathFeature
| AnnotateRevisionFeature
}; };
Q_DECLARE_FLAGS(SupportedFeatures, SupportedFeature) Q_DECLARE_FLAGS(SupportedFeatures, SupportedFeature)
@@ -64,7 +66,8 @@ public:
unsigned int synchronousBinaryVersion() const; unsigned int synchronousBinaryVersion() const;
BranchInfo synchronousCurrentBranch(const QString &workingDirectory); BranchInfo synchronousCurrentBranch(const QString &workingDirectory);
QList<BranchInfo> synchronousBranchQuery(const QString &workingDirectory); QList<BranchInfo> synchronousBranchQuery(const QString &workingDirectory);
RevisionInfo synchronousRevisionQuery(const QString &workingDirectory, const QString &id = QString()); RevisionInfo synchronousRevisionQuery(const QString &workingDirectory, const QString &id = QString(),
bool getCommentMsg = false) const;
QStringList synchronousTagQuery(const QString &workingDirectory, const QString &id = QString()); QStringList synchronousTagQuery(const QString &workingDirectory, const QString &id = QString());
RepositorySettings synchronousSettingsQuery(const QString &workingDirectory); RepositorySettings synchronousSettingsQuery(const QString &workingDirectory);
bool synchronousSetSetting(const QString &workingDirectory, const QString &property, bool synchronousSetSetting(const QString &workingDirectory, const QString &property,
@@ -113,6 +116,7 @@ public:
private: private:
static QList<BranchInfo> branchListFromOutput(const QString &output, const BranchInfo::BranchFlags defaultFlags = 0); static QList<BranchInfo> branchListFromOutput(const QString &output, const BranchInfo::BranchFlags defaultFlags = 0);
static QStringList parseRevisionCommentLine(const QString &commentLine);
QString sanitizeFossilOutput(const QString &output) const; QString sanitizeFossilOutput(const QString &output) const;
QString vcsCommandString(VcsCommandTag cmd) const final; QString vcsCommandString(VcsCommandTag cmd) const final;

View File

@@ -124,8 +124,48 @@ QString FossilEditorWidget::changeUnderCursor(const QTextCursor &cursorIn) const
return QString(); return QString();
} }
QString FossilEditorWidget::decorateVersion(const QString &revision) const
{
static const int shortChangesetIdSize(10);
static const int maxTextSize(120);
VcsBase::BaseAnnotationHighlighter *FossilEditorWidget::createAnnotationHighlighter(const QSet<QString> &changes) const const QFileInfo fi(source());
const QString workingDirectory = fi.absolutePath();
FossilClient *client = FossilPlugin::instance()->client();
RevisionInfo revisionInfo =
client->synchronousRevisionQuery(workingDirectory, revision, true);
// format: 'revision (committer "comment...")'
QString output = revision.left(shortChangesetIdSize)
+ " (" + revisionInfo.committer
+ " \"" + revisionInfo.commentMsg.left(maxTextSize);
if (output.size() > maxTextSize) {
output.truncate(maxTextSize - 3);
output.append("...");
}
output.append("\")");
return output;
}
QStringList FossilEditorWidget::annotationPreviousVersions(const QString &revision) const
{
QStringList revisions;
const QFileInfo fi(source());
const QString workingDirectory = fi.absolutePath();
FossilClient *client = FossilPlugin::instance()->client();
RevisionInfo revisionInfo =
client->synchronousRevisionQuery(workingDirectory, revision);
if (revisionInfo.parentId.isEmpty())
return QStringList();
revisions.append(revisionInfo.parentId);
revisions.append(revisionInfo.mergeParentIds);
return revisions;
}
VcsBase::BaseAnnotationHighlighter *FossilEditorWidget::createAnnotationHighlighter(
const QSet<QString> &changes) const
{ {
return new FossilAnnotationHighlighter(changes); return new FossilAnnotationHighlighter(changes);
} }

View File

@@ -43,7 +43,10 @@ public:
private: private:
QSet<QString> annotationChanges() const final; QSet<QString> annotationChanges() const final;
QString changeUnderCursor(const QTextCursor &cursor) const final; QString changeUnderCursor(const QTextCursor &cursor) const final;
VcsBase::BaseAnnotationHighlighter *createAnnotationHighlighter(const QSet<QString> &changes) const final; QString decorateVersion(const QString &revision) const final;
QStringList annotationPreviousVersions(const QString &revision) const final;
VcsBase::BaseAnnotationHighlighter *createAnnotationHighlighter(
const QSet<QString> &changes) const final;
FossilEditorWidgetPrivate *d; FossilEditorWidgetPrivate *d;
}; };

View File

@@ -285,8 +285,9 @@ void FossilPlugin::logCurrentFile()
if (features.testFlag(FossilClient::TimelineWidthFeature)) if (features.testFlag(FossilClient::TimelineWidthFeature))
extraOptions << "-W" << QString::number(m_client->settings().intValue(FossilSettings::timelineWidthKey)); extraOptions << "-W" << QString::number(m_client->settings().intValue(FossilSettings::timelineWidthKey));
// annotate only supported for current revision, so disable context menu // disable annotate context menu for older client versions, used to be supported for current revision only
bool enableAnnotationContextMenu = false; bool enableAnnotationContextMenu = features.testFlag(FossilClient::AnnotateRevisionFeature);
m_client->logCurrentFile(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()), m_client->logCurrentFile(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()),
extraOptions, enableAnnotationContextMenu); extraOptions, enableAnnotationContextMenu);
} }

View File

@@ -28,9 +28,14 @@
namespace Fossil { namespace Fossil {
namespace Internal { namespace Internal {
RevisionInfo::RevisionInfo(const QString &revisionId, const QString &parent) : RevisionInfo::RevisionInfo(const QString &revisionId, const QString &parent,
const QStringList &mergeParents, const QString &comment,
const QString &user) :
id(revisionId), id(revisionId),
parentId(parent) parentId(parent),
mergeParentIds(mergeParents),
commentMsg(comment),
committer(user)
{ } { }
} // namespace Internal } // namespace Internal

View File

@@ -36,10 +36,15 @@ namespace Internal {
class RevisionInfo class RevisionInfo
{ {
public: public:
explicit RevisionInfo(const QString &revisionId = QString(), const QString &parent = QString()); explicit RevisionInfo(const QString &revisionId = QString(), const QString &parent = QString(),
const QStringList &mergeParents = QStringList(),
const QString &comment = QString(), const QString &user = QString());
const QString id; const QString id;
const QString parentId; const QString parentId;
const QStringList mergeParentIds;
const QString commentMsg;
const QString committer;
}; };