File indexing completed on 2024-04-21 04:34:34

0001 /*
0002     This file is part of KDevelop
0003 
0004     Copyright 2016 Sergey Kalinichev <kalinichev.so.0@gmail.com>
0005 
0006     This library is free software; you can redistribute it and/or
0007     modify it under the terms of the GNU Library General Public
0008     License as published by the Free Software Foundation; either
0009     version 2 of the License, or (at your option) any later version.
0010 
0011     This library is distributed in the hope that it will be useful,
0012     but WITHOUT ANY WARRANTY; without even the implied warranty of
0013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014     Library General Public License for more details.
0015 
0016     You should have received a copy of the GNU Library General Public License
0017     along with this library; see the file COPYING.LIB.  If not, write to
0018     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0019     Boston, MA 02110-1301, USA.
0020 */
0021 
0022 #include "mercurialannotatejob.h"
0023 
0024 #include <vcs/vcsannotation.h>
0025 #include <vcs/dvcs/dvcsjob.h>
0026 
0027 #include <QDateTime>
0028 
0029 #include "debug.h"
0030 
0031 using namespace KDevelop;
0032 
0033 MercurialAnnotateJob::MercurialAnnotateJob(const QDir &workingDir, const VcsRevision& revision, const QUrl& location, MercurialPlugin *parent)
0034     : MercurialJob(workingDir, parent, JobType::Annotate),
0035     m_revision(revision),
0036     m_location(location)
0037 {}
0038 
0039 void MercurialAnnotateJob::start()
0040 {
0041     m_status = JobRunning;
0042 
0043     DVcsJob *job = new DVcsJob(m_workingDir, vcsPlugin(), KDevelop::OutputJob::Silent);
0044     if (Q_UNLIKELY(m_testCase == TestCase::Status)) {
0045          *job << "some_command_that_doesnt_exist";
0046     } else {
0047         *job << "hg" << "status" << "-m"  << "-a" << "-n";
0048 
0049         *job << "--" << m_location.toLocalFile();
0050     }
0051 
0052     connect(job, &DVcsJob::resultsReady, this, &MercurialAnnotateJob::parseStatusResult);
0053     connect(job, &KJob::finished, this, &MercurialAnnotateJob::subJobFinished);
0054     m_job = job;
0055     job->start();
0056 }
0057 
0058 void MercurialAnnotateJob::parseCommitResult(KDevelop::VcsJob* j)
0059 {
0060     DVcsJob *job = static_cast<DVcsJob *>(j);
0061     if (job->status() != VcsJob::JobSucceeded)
0062         return setFail();
0063 
0064     launchAnnotateJob();
0065 }
0066 
0067 void MercurialAnnotateJob::launchAnnotateJob() const
0068 {
0069     DVcsJob *annotateJob = new DVcsJob(m_workingDir, vcsPlugin(), KDevelop::OutputJob::Silent);
0070     if (Q_UNLIKELY(m_testCase == TestCase::Annotate)) {
0071          *annotateJob << "some_command_that_doesnt_exist";
0072     } else {
0073         *annotateJob << "hg" << "annotate" << "-n" << "-d";
0074 
0075         *annotateJob << "--" << m_location.toLocalFile();
0076     }
0077 
0078     connect(annotateJob, &DVcsJob::resultsReady, this, &MercurialAnnotateJob::parseAnnotateOutput);
0079     connect(annotateJob, &KJob::finished, this, &MercurialAnnotateJob::subJobFinished);
0080     m_job = annotateJob;
0081     annotateJob->start();
0082 }
0083 
0084 void MercurialAnnotateJob::parseStatusResult(KDevelop::VcsJob* j)
0085 {
0086     DVcsJob *job = static_cast<DVcsJob *>(j);
0087     if (job->status() != VcsJob::JobSucceeded)
0088         return setFail();
0089 
0090     if (job->output().isEmpty()) {
0091         launchAnnotateJob();
0092     } else {
0093         m_hasModifiedFile = true;
0094         DVcsJob *commitJob = new DVcsJob(m_workingDir, vcsPlugin(), KDevelop::OutputJob::Silent);
0095         if (Q_UNLIKELY(m_testCase == TestCase::Commit)) {
0096             *commitJob << "some_command_that_doesnt_exist";
0097         } else {
0098             *commitJob << "hg" << "commit" << "-u"  << "not.committed.yet" << "-m" << "Not Committed Yet";
0099 
0100             *commitJob << "--" << m_location.toLocalFile();
0101         }
0102         connect(commitJob, &DVcsJob::resultsReady, this, &MercurialAnnotateJob::parseCommitResult);
0103         connect(commitJob, &KJob::finished, this, &MercurialAnnotateJob::subJobFinished);
0104         m_job = commitJob;
0105         commitJob->start();
0106     }
0107 }
0108 
0109 void MercurialAnnotateJob::parseStripResult(KDevelop::VcsJob* /*job*/)
0110 {
0111     setSuccess();
0112 }
0113 
0114 QVariant MercurialAnnotateJob::fetchResults()
0115 {
0116     return m_annotations;
0117 }
0118 
0119 void MercurialAnnotateJob::parseAnnotateOutput(VcsJob *j)
0120 {
0121     DVcsJob *job = static_cast<DVcsJob *>(j);
0122     if (job->status() != VcsJob::JobSucceeded)
0123         return setFail();
0124 
0125     QStringList lines = job->output().split('\n');
0126     lines.removeLast();
0127 
0128     static const QString reAnnotPat("\\s*(\\d+)\\s+(\\w+ \\w+ \\d\\d \\d\\d:\\d\\d:\\d\\d \\d\\d\\d\\d .\\d\\d\\d\\d): ([^\n]*)");
0129     QRegExp reAnnot(reAnnotPat, Qt::CaseSensitive, QRegExp::RegExp2);
0130     unsigned int lineNumber = 0;
0131     foreach (const QString& line, lines) {
0132         if (!reAnnot.exactMatch(line)) {
0133             qCDebug(PLUGIN_MERCURIAL) << "Could not parse annotation line: \"" << line << '\"';
0134             return setFail();
0135         }
0136         VcsAnnotationLine annotation;
0137         annotation.setLineNumber(lineNumber++);
0138         annotation.setText(reAnnot.cap(3));
0139 
0140         bool success = false;
0141         qlonglong rev = reAnnot.cap(1).toLongLong(&success);
0142         if (!success) {
0143             qCDebug(PLUGIN_MERCURIAL) << "Could not parse revision in annotation line: \"" << line << '\"';
0144             return setFail();
0145         }
0146 
0147         QDateTime dt = QDateTime::fromString(reAnnot.cap(2).left(reAnnot.cap(2).lastIndexOf(" ")), "ddd MMM dd hh:mm:ss yyyy");
0148         //qCDebug(PLUGIN_MERCURIAL) << reAnnot.cap(2).left(reAnnot.cap(2).lastIndexOf(" ")) << dt;
0149         Q_ASSERT(dt.isValid());
0150         annotation.setDate(dt);
0151 
0152         VcsRevision vcsrev;
0153         vcsrev.setRevisionValue(rev, VcsRevision::GlobalNumber);
0154         annotation.setRevision(vcsrev);
0155         m_annotations.push_back(qVariantFromValue(annotation));
0156     }
0157 
0158     for (const auto& annotation: m_annotations) {
0159         const auto revision = static_cast<MercurialPlugin*>(vcsPlugin())->toMercurialRevision(annotation.value<VcsAnnotationLine>().revision());
0160         if (!m_revisionsToLog.contains(revision)) {
0161             m_revisionsToLog.insert(revision);
0162         }
0163     }
0164 
0165     nextPartOfLog();
0166 }
0167 
0168 void MercurialAnnotateJob::nextPartOfLog()
0169 {
0170     m_status = JobRunning;
0171 
0172     DVcsJob *logJob = new DVcsJob(m_workingDir, vcsPlugin(), KDevelop::OutputJob::Silent);
0173     if (Q_UNLIKELY(m_testCase == TestCase::Log)) {
0174          *logJob << "some_command_that_doesnt_exist";
0175     } else {
0176         *logJob << "hg" << "log" << "--template" << "{rev}\\_%{desc|firstline}\\_%{author}\\_%";
0177     }
0178 
0179     int numRevisions = 0;
0180     for (auto it = m_revisionsToLog.begin(); it != m_revisionsToLog.end();) {
0181         *logJob << "-r" << *it;
0182         it = m_revisionsToLog.erase(it);
0183 
0184         if (++numRevisions >= 200) {
0185             // Prevent mercurial crash
0186             break;
0187         }
0188     }
0189 
0190     connect(logJob, &DVcsJob::resultsReady, this, &MercurialAnnotateJob::parseLogOutput);
0191     connect(logJob, &KJob::finished, this, &MercurialAnnotateJob::subJobFinished);
0192     m_job = logJob;
0193     logJob->start();
0194 }
0195 
0196 void MercurialAnnotateJob::parseLogOutput(KDevelop::VcsJob* j)
0197 {
0198     DVcsJob *job = static_cast<DVcsJob *>(j);
0199 
0200     auto items = job->output().split("\\_%");
0201     items.removeLast();
0202 
0203     if (items.size() % 3) {
0204         qCDebug(PLUGIN_MERCURIAL) << "Cannot parse annotate log: unexpected number of entries" << items.size() << m_annotations.size();
0205         return setFail();
0206     }
0207 
0208     for (auto it = items.constBegin(); it != items.constEnd(); it += 3) {
0209         if (!m_revisionsCache.contains(*it)) {
0210             m_revisionsCache[*it] = {*(it + 1), *(it + 2)};
0211         }
0212     }
0213 
0214     if (!m_revisionsToLog.isEmpty()) {
0215         // TODO: We can show what we've got so far
0216         return nextPartOfLog();
0217     }
0218 
0219     for (int idx = 0; idx < m_annotations.size(); idx++) {
0220         auto annotationLine = m_annotations[idx].value<VcsAnnotationLine>();
0221         const auto revision = static_cast<MercurialPlugin*>(vcsPlugin())->toMercurialRevision(annotationLine.revision());
0222 
0223         if (!m_revisionsCache.contains(revision)) {
0224             Q_ASSERT(m_revisionsCache.contains(revision));
0225             continue;
0226         }
0227         qCDebug(PLUGIN_MERCURIAL) << m_revisionsCache.value(revision).first << m_revisionsCache.value(revision).second;
0228         if (m_revisionsCache.value(revision).first.isEmpty() && m_revisionsCache.value(revision).second.isEmpty()) {
0229             qCDebug(PLUGIN_MERCURIAL) << "Strange revision" << revision;
0230             continue;
0231         }
0232         annotationLine.setCommitMessage(m_revisionsCache.value(revision).first);
0233         annotationLine.setAuthor(m_revisionsCache.value(revision).second);
0234         m_annotations[idx] = qVariantFromValue(annotationLine);
0235     }
0236 
0237     if (m_hasModifiedFile) {
0238         DVcsJob* stripJob = new DVcsJob(m_workingDir, vcsPlugin(), KDevelop::OutputJob::Silent);
0239         if (Q_UNLIKELY(m_testCase == TestCase::Strip)) {
0240             *stripJob << "some_command_that_doesnt_exist";
0241         } else {
0242             *stripJob << "hg" << "--config" << "extensions.mq=" << "strip" << "-k" << "tip";
0243         }
0244 
0245         connect(stripJob, &DVcsJob::resultsReady, this, &MercurialAnnotateJob::parseStripResult);
0246         connect(stripJob, &KJob::finished, this, &MercurialAnnotateJob::subJobFinished);
0247         m_job = stripJob;
0248         stripJob->start();
0249     } else {
0250         setSuccess();
0251     }
0252 }
0253 
0254 void MercurialAnnotateJob::subJobFinished(KJob* job)
0255 {
0256     if (job->error()) {
0257         setFail();
0258     }
0259 }