File indexing completed on 2024-05-05 04:39:01

0001 /*
0002     SPDX-FileCopyrightText: 2013-2014 Maciej Poleski
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "bzrannotatejob.h"
0008 
0009 #include <functional>
0010 
0011 #include <QTimer>
0012 #include <QDateTime>
0013 #include <QDir>
0014 
0015 #include <vcs/dvcs/dvcsjob.h>
0016 #include <vcs/vcsannotation.h>
0017 #include <vcs/vcsrevision.h>
0018 #include <interfaces/iplugin.h>
0019 
0020 using namespace KDevelop;
0021 
0022 BzrAnnotateJob::BzrAnnotateJob(const QDir& workingDir, const QString& revisionSpec, const QUrl& localLocation, KDevelop::IPlugin* parent, KDevelop::OutputJob::OutputJobVerbosity verbosity)
0023     : VcsJob(parent, verbosity), m_workingDir(workingDir), m_revisionSpec(revisionSpec), m_localLocation(localLocation), m_vcsPlugin(parent), m_status(KDevelop::VcsJob::JobNotStarted)
0024 {
0025     setType(JobType::Annotate);
0026     setCapabilities(Killable);
0027 }
0028 
0029 bool BzrAnnotateJob::doKill()
0030 {
0031     m_status = KDevelop::VcsJob::JobCanceled;
0032     if (m_job)
0033         return m_job->kill(KJob::Quietly);
0034     else
0035         return true;
0036 }
0037 
0038 void BzrAnnotateJob::start()
0039 {
0040     if (m_status != KDevelop::VcsJob::JobNotStarted)
0041         return;
0042     auto* job = new KDevelop::DVcsJob(m_workingDir, m_vcsPlugin, KDevelop::OutputJob::Silent);
0043     *job << "bzr" << "annotate" << "--all" << m_revisionSpec << m_localLocation;
0044     connect(job, &DVcsJob::readyForParsing, this, &BzrAnnotateJob::parseBzrAnnotateOutput);
0045     m_status = VcsJob::JobRunning;
0046     m_job = job;
0047     job->start();
0048 }
0049 
0050 void BzrAnnotateJob::parseBzrAnnotateOutput(KDevelop::DVcsJob* job)
0051 {
0052     m_outputLines = job->output().split(QLatin1Char('\n'));
0053     m_currentLine = 0;
0054     if (m_status == KDevelop::VcsJob::JobRunning)
0055         QTimer::singleShot(0, this, &BzrAnnotateJob::parseNextLine);
0056 }
0057 
0058 void BzrAnnotateJob::parseNextLine()
0059 {
0060     for(;;)
0061     {
0062         Q_ASSERT(m_currentLine<=m_outputLines.size());
0063         if (m_currentLine == m_outputLines.size()) {
0064             m_status = KDevelop::VcsJob::JobSucceeded;
0065             emitResult();
0066             emit resultsReady(this);
0067             break;
0068         }
0069         QString currentLine = m_outputLines[m_currentLine];
0070         if (currentLine.isEmpty()) {
0071             ++m_currentLine;
0072             continue;
0073         }
0074         bool revOk;
0075         auto revision = currentLine.leftRef(currentLine.indexOf(QLatin1Char(' '))).toULong(&revOk);
0076         if (!revOk) {
0077             // Future compatibility - not a revision yet
0078             ++m_currentLine;
0079             continue;
0080         }
0081         auto i = m_commits.find(revision);
0082         if (i != m_commits.end()) {
0083             KDevelop::VcsAnnotationLine line;
0084             line.setAuthor(i.value().author());
0085             line.setCommitMessage(i.value().message());
0086             line.setDate(i.value().date());
0087             line.setLineNumber(m_currentLine);
0088             line.setRevision(i.value().revision());
0089             m_results.append(QVariant::fromValue(line));
0090             ++m_currentLine;
0091             continue;
0092         } else {
0093             prepareCommitInfo(revision);
0094             break;  //Will reenter this function when commit info will be ready
0095         }
0096     }
0097 }
0098 
0099 void BzrAnnotateJob::prepareCommitInfo(std::size_t revision)
0100 {
0101     if (m_status != KDevelop::VcsJob::JobRunning)
0102         return;
0103     auto* job = new KDevelop::DVcsJob(m_workingDir, m_vcsPlugin, KDevelop::OutputJob::Silent);
0104     job->setType(KDevelop::VcsJob::Log);
0105     *job << "bzr" << "log" << "--long" << "-r" << QString::number(revision);
0106     connect(job, &DVcsJob::readyForParsing, this, &BzrAnnotateJob::parseBzrLog);
0107     m_job = job;
0108     job->start();
0109 }
0110 
0111 /*
0112  * This is slightly different from BazaarUtils::parseBzrLogPart(...).
0113  * This function parses only commit general info. It does not parse signle
0114  * actions. In fact output parsed by this function is slightly different
0115  * from output parsed by BazaarUtils. As a result parsing this output using
0116  * BazaarUtils would yield different results.
0117  * NOTE: This is all about parsing 'message'.
0118  */
0119 void BzrAnnotateJob::parseBzrLog(KDevelop::DVcsJob* job)
0120 {
0121     const QStringList outputLines = job->output().split(QLatin1Char('\n'));
0122     KDevelop::VcsEvent commitInfo;
0123     int revision=-1;
0124     bool atMessage = false;
0125     QString message;
0126     for (const QString& line : outputLines) {
0127         if (!atMessage) {
0128             if (line.startsWith(QLatin1String("revno"))) {
0129                 QString revno = line.mid(QStringLiteral("revno: ").length());
0130                 // In future there is possibility that "revno: " will change to
0131                 // "revno??". If that's all, then we recover matching only
0132                 // "revno" prefix and assuming placeholder of length 2 (": " or
0133                 // "??").
0134                 // The same below with exception of "committer" which possibly
0135                 // can have also some suffix which changes meaning like
0136                 // "committer-some_property: "...
0137                 revno = revno.left(revno.indexOf(QLatin1Char(' ')));
0138                 revision = revno.toInt();
0139                 KDevelop::VcsRevision revision;
0140                 revision.setRevisionValue(revno.toLongLong(), KDevelop::VcsRevision::GlobalNumber);
0141                 commitInfo.setRevision(revision);
0142             } else if (line.startsWith(QLatin1String("committer: "))) {
0143                 QString commiter = line.mid(QStringLiteral("committer: ").length());
0144                 commitInfo.setAuthor(commiter);     // Author goes after committer, but only if is different
0145             } else if (line.startsWith(QLatin1String("author"))) {
0146                 QString author = line.mid(QStringLiteral("author: ").length());
0147                 commitInfo.setAuthor(author);       // It may override committer (In fact committer is not supported by VcsEvent)
0148             } else if (line.startsWith(QLatin1String("timestamp"))) {
0149                 const QString formatString = QStringLiteral("yyyy-MM-dd hh:mm:ss");
0150                 QString timestamp = line.mid(QStringLiteral("timestamp: ddd ").length(), formatString.length());
0151                 commitInfo.setDate(QDateTime::fromString(timestamp, formatString));
0152             } else if (line.startsWith(QLatin1String("message"))) {
0153                 atMessage = true;
0154             }
0155         } else {
0156             message += line.trimmed() + QLatin1Char('\n');
0157         }
0158     }
0159     if (atMessage)
0160         commitInfo.setMessage(message.trimmed());
0161     Q_ASSERT(revision!=-1);
0162     m_commits[revision] = commitInfo;
0163     // Invoke from event loop to protect against stack overflow (it could happen
0164     // on very big files with very big history of changes if tail-recursion
0165     // optimization had failed here).
0166     QTimer::singleShot(0, this, &BzrAnnotateJob::parseNextLine);
0167 }
0168 
0169 QVariant BzrAnnotateJob::fetchResults()
0170 {
0171     return m_results;
0172 }
0173 
0174 KDevelop::VcsJob::JobStatus BzrAnnotateJob::status() const
0175 {
0176     return m_status;
0177 }
0178 
0179 KDevelop::IPlugin* BzrAnnotateJob::vcsPlugin() const
0180 {
0181     return m_vcsPlugin;
0182 }
0183 
0184 #include "moc_bzrannotatejob.cpp"