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 }