File indexing completed on 2024-05-05 16:46:07

0001 /*
0002     SPDX-FileCopyrightText: 2010 Morten Danielsen Volden <mvolden2@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "perforceplugin.h"
0008 #include "ui/perforceimportmetadatawidget.h"
0009 #include "debug.h"
0010 
0011 #include <KLocalizedString>
0012 #include <QFileInfo>
0013 #include <QDateTime>
0014 #include <QDir>
0015 #include <QProcessEnvironment>
0016 #include <QMenu>
0017 
0018 #include <KMessageBox>
0019 #include <vcs/vcsjob.h>
0020 #include <vcs/vcsrevision.h>
0021 #include <vcs/vcsstatusinfo.h>
0022 #include <vcs/vcsevent.h>
0023 #include <vcs/dvcs/dvcsjob.h>
0024 #include <vcs/vcsannotation.h>
0025 #include <vcs/widgets/standardvcslocationwidget.h>
0026 
0027 #include <interfaces/context.h>
0028 #include <interfaces/contextmenuextension.h>
0029 #include <interfaces/icore.h>
0030 #include <interfaces/iruncontroller.h>
0031 
0032 #include <vcs/vcspluginhelper.h>
0033 
0034 using namespace KDevelop;
0035 
0036 namespace
0037 {
0038 QString toRevisionName(const KDevelop::VcsRevision& rev, const QString& currentRevision=QString())
0039 {
0040     bool ok;
0041     int previous = currentRevision.toInt(&ok);
0042     previous--;
0043     QString tmp;
0044     switch(rev.revisionType()) {
0045         case VcsRevision::Special:
0046             switch(rev.revisionValue().value<VcsRevision::RevisionSpecialType>()) {
0047                 case VcsRevision::Head:
0048                     return QStringLiteral("#head");
0049                 case VcsRevision::Base:
0050                     return QStringLiteral("#have");
0051                 case VcsRevision::Working:
0052                     return QStringLiteral("#have");
0053                 case VcsRevision::Previous:
0054                     Q_ASSERT(!currentRevision.isEmpty());
0055                     tmp.setNum(previous);
0056                     tmp.prepend(QLatin1Char('#'));
0057                     return tmp;
0058                 case VcsRevision::Start:
0059                     return QString();
0060                 case VcsRevision::UserSpecialType: //Not used
0061                     Q_ASSERT(false && "i don't know how to do that");
0062             }
0063             break;
0064         case VcsRevision::GlobalNumber:
0065             tmp.append(QLatin1Char('#') + rev.revisionValue().toString());
0066             return tmp;
0067         case VcsRevision::Date:
0068         case VcsRevision::FileNumber:
0069         case VcsRevision::Invalid:
0070         case VcsRevision::UserType:
0071             Q_ASSERT(false);
0072     }
0073     return QString();
0074 }
0075 
0076 
0077 VcsItemEvent::Actions actionsFromString(QString const& changeDescription)
0078 {
0079     if(changeDescription == QLatin1String("add"))
0080         return VcsItemEvent::Added;
0081     if(changeDescription == QLatin1String("delete"))
0082         return VcsItemEvent::Deleted;
0083     return VcsItemEvent::Modified;
0084 }
0085 
0086 QDir urlDir(const QUrl& url)
0087 {
0088     QFileInfo f(url.toLocalFile());
0089     if(f.isDir())
0090         return QDir(url.toLocalFile());
0091     else
0092         return f.absoluteDir();
0093 }
0094 
0095 }
0096 
0097 PerforcePlugin::PerforcePlugin(QObject* parent, const QVariantList&):
0098     KDevelop::IPlugin(QStringLiteral("kdevperforce"), parent)
0099     , m_common(new KDevelop::VcsPluginHelper(this, this))
0100     , m_perforceConfigName(QStringLiteral("p4config.txt"))
0101     , m_perforceExecutable(QStringLiteral("p4"))
0102     , m_edit_action(nullptr)
0103 {
0104     QProcessEnvironment currentEviron(QProcessEnvironment::systemEnvironment());
0105     QString tmp(currentEviron.value(QStringLiteral("P4CONFIG")));
0106     if (tmp.isEmpty()) {
0107         // We require the P4CONFIG variable to be set because the perforce command line client will need it
0108         setErrorDescription(i18n("The variable P4CONFIG is not set. Is perforce installed on the system?"));
0109         return;
0110     } else {
0111         m_perforceConfigName = tmp;
0112     }
0113     qCDebug(PLUGIN_PERFORCE) << "The value of P4CONFIG is : " << tmp;
0114 }
0115 
0116 PerforcePlugin::~PerforcePlugin()
0117 {
0118 }
0119 
0120 QString PerforcePlugin::name() const
0121 {
0122     return i18n("Perforce");
0123 }
0124 
0125 KDevelop::VcsImportMetadataWidget* PerforcePlugin::createImportMetadataWidget(QWidget* parent)
0126 {
0127     return new PerforceImportMetadataWidget(parent);
0128 }
0129 
0130 bool PerforcePlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation)
0131 {
0132     Q_UNUSED(remoteLocation);
0133     // TODO
0134     return false;
0135 }
0136 
0137 bool PerforcePlugin::isValidDirectory(const QUrl & dirPath)
0138 {
0139     const QFileInfo finfo(dirPath.toLocalFile());
0140     QDir dir = finfo.isDir() ? QDir(dirPath.toLocalFile()) : finfo.absoluteDir();
0141 
0142     do {
0143         if (dir.exists(m_perforceConfigName)) {
0144             return true;
0145         }
0146     } while (dir.cdUp());
0147     return false;
0148 }
0149 
0150 bool PerforcePlugin::isVersionControlled(const QUrl& localLocation)
0151 {
0152     QFileInfo fsObject(localLocation.toLocalFile());
0153     if (fsObject.isDir()) {
0154         return isValidDirectory(localLocation);
0155     }
0156     return parseP4fstat(fsObject, KDevelop::OutputJob::Silent);
0157 }
0158 
0159 DVcsJob* PerforcePlugin::p4fstatJob(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity)
0160 {
0161     auto* job = new DVcsJob(curFile.absolutePath(), this, verbosity);
0162     setEnvironmentForJob(job, curFile);
0163     *job << m_perforceExecutable << "fstat" << curFile.fileName();
0164     return job;
0165 }
0166 
0167 bool PerforcePlugin::parseP4fstat(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity)
0168 {
0169     QScopedPointer<DVcsJob> job(p4fstatJob(curFile, verbosity));
0170     if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) {
0171         qCDebug(PLUGIN_PERFORCE) << "Perforce returned: " << job->output();
0172         if (!job->output().isEmpty())
0173             return true;
0174     }
0175     return false;
0176 }
0177 
0178 QString PerforcePlugin::getRepositoryName(const QFileInfo& curFile)
0179 {
0180     const QString DEPOT_FILE_STR(QStringLiteral("... depotFile "));
0181     QString ret;
0182     QScopedPointer<DVcsJob> job(p4fstatJob(curFile, KDevelop::OutputJob::Silent));
0183     if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) {
0184         if (!job->output().isEmpty()) {
0185             const QStringList outputLines = job->output().split(QLatin1Char('\n'), Qt::SkipEmptyParts);
0186             for (const QString& line : outputLines) {
0187                 int idx(line.indexOf(DEPOT_FILE_STR));
0188                 if (idx != -1) {
0189                     ret = line.mid(DEPOT_FILE_STR.size());
0190                     return ret;
0191                 }
0192             }
0193         }
0194     }
0195 
0196     return ret;
0197 }
0198 
0199 KDevelop::VcsJob* PerforcePlugin::repositoryLocation(const QUrl& /*localLocation*/)
0200 {
0201     return nullptr;
0202 }
0203 
0204 KDevelop::VcsJob* PerforcePlugin::add(const QList<QUrl>& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
0205 {
0206     QFileInfo curFile(localLocations.front().toLocalFile());
0207     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
0208     setEnvironmentForJob(job, curFile);
0209     *job << m_perforceExecutable << "add" << localLocations;
0210 
0211     return job;
0212 }
0213 
0214 KDevelop::VcsJob* PerforcePlugin::remove(const QList<QUrl>& /*localLocations*/)
0215 {
0216     return nullptr;
0217 }
0218 
0219 KDevelop::VcsJob* PerforcePlugin::copy(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDstn*/)
0220 {
0221     return nullptr;
0222 }
0223 
0224 KDevelop::VcsJob* PerforcePlugin::move(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDst*/)
0225 {
0226     return nullptr;
0227 }
0228 
0229 KDevelop::VcsJob* PerforcePlugin::status(const QList<QUrl>& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
0230 {
0231     if (localLocations.count() != 1) {
0232         KMessageBox::error(nullptr, i18n("Please select only one item for this operation"));
0233         return nullptr;
0234     }
0235 
0236     QFileInfo curFile(localLocations.front().toLocalFile());
0237 
0238     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
0239     setEnvironmentForJob(job, curFile);
0240     *job << m_perforceExecutable << "fstat" << curFile.fileName();
0241     connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4StatusOutput);
0242 
0243     return job;
0244 }
0245 
0246 KDevelop::VcsJob* PerforcePlugin::revert(const QList<QUrl>& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
0247 {
0248     if (localLocations.count() != 1) {
0249         KMessageBox::error(nullptr, i18n("Please select only one item for this operation"));
0250         return nullptr;
0251     }
0252 
0253     QFileInfo curFile(localLocations.front().toLocalFile());
0254 
0255     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
0256     setEnvironmentForJob(job, curFile);
0257     *job << m_perforceExecutable << "revert" << curFile.fileName();
0258 
0259     return job;
0260 
0261 }
0262 
0263 KDevelop::VcsJob* PerforcePlugin::update(const QList<QUrl>& localLocations, const KDevelop::VcsRevision& /*rev*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
0264 {
0265     QFileInfo curFile(localLocations.front().toLocalFile());
0266 
0267     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
0268     setEnvironmentForJob(job, curFile);
0269     //*job << m_perforceExecutable << "-p" << "127.0.0.1:1666" << "info"; - Let's keep this for now it's very handy for debugging
0270     QString fileOrDirectory;
0271     if (curFile.isDir())
0272         fileOrDirectory = curFile.absolutePath() + QLatin1String("/...");
0273     else
0274         fileOrDirectory = curFile.fileName();
0275     *job << m_perforceExecutable << "sync" << fileOrDirectory;
0276     return job;
0277 }
0278 
0279 KDevelop::VcsJob* PerforcePlugin::commit(const QString& message, const QList<QUrl>& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
0280 {
0281     if (localLocations.empty() || message.isEmpty())
0282         return errorsFound(i18n("No files or message specified"));
0283 
0284 
0285     QFileInfo curFile(localLocations.front().toLocalFile());
0286 
0287     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
0288     setEnvironmentForJob(job, curFile);
0289     *job << m_perforceExecutable << "submit" << "-d" << message << localLocations;
0290 
0291     return job;
0292 }
0293 
0294 KDevelop::VcsJob* PerforcePlugin::diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
0295 {
0296     QFileInfo curFile(fileOrDirectory.toLocalFile());
0297     QString depotSrcFileName = getRepositoryName(curFile);
0298     QString depotDstFileName = depotSrcFileName;
0299     depotSrcFileName.append(toRevisionName(srcRevision, dstRevision.prettyValue())); // dstRevision actually contains the number that we want to take previous of
0300 
0301     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
0302     setEnvironmentForJob(job, curFile);
0303     switch (dstRevision.revisionType()) {
0304     case VcsRevision::FileNumber:
0305     case VcsRevision::GlobalNumber:
0306         depotDstFileName.append(QLatin1Char('#') + dstRevision.prettyValue());
0307         *job << m_perforceExecutable << "diff2" << "-u" << depotSrcFileName << depotDstFileName;
0308         break;
0309     case VcsRevision::Special:
0310         switch (dstRevision.revisionValue().value<VcsRevision::RevisionSpecialType>()) {
0311         case VcsRevision::Working:
0312             *job << m_perforceExecutable << "diff" << "-du" << depotSrcFileName;
0313             break;
0314         case VcsRevision::Start:
0315         case VcsRevision::UserSpecialType:
0316         default:
0317             break;
0318         }
0319         break;
0320     default:
0321         break;
0322     }
0323 
0324     connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4DiffOutput);
0325     return job;
0326 }
0327 
0328 KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, long unsigned int limit)
0329 {
0330     static QString lastSeenChangeList;
0331     QFileInfo curFile(localLocation.toLocalFile());
0332     QString localLocationAndRevStr = localLocation.toLocalFile(); 
0333     
0334     auto* job = new DVcsJob(urlDir(localLocation), this, KDevelop::OutputJob::Verbose);
0335     setEnvironmentForJob(job, curFile);
0336     *job << m_perforceExecutable << "filelog" << "-lit";
0337     if(limit > 0)
0338         *job << QStringLiteral("-m %1").arg(limit);
0339     
0340     if (curFile.isDir()) {
0341         localLocationAndRevStr.append(QLatin1String("/..."));
0342     } 
0343     QString revStr = toRevisionName(rev, QString());
0344     if(!revStr.isEmpty()) {
0345         // This is not too nice, but perforce argument for restricting output from filelog does not Work :-(
0346         // So putting this in so we do not end up in infinite loop calling log,
0347         if(revStr == lastSeenChangeList) {
0348             localLocationAndRevStr.append(QLatin1String("#none"));
0349             lastSeenChangeList.clear();
0350         } else { 
0351             localLocationAndRevStr.append(revStr);
0352             lastSeenChangeList = revStr;
0353         }
0354     }
0355     *job << localLocationAndRevStr;
0356     qCDebug(PLUGIN_PERFORCE) << "Issuing the following command to p4: " << job->dvcsCommand();
0357     connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4LogOutput);
0358     return job;
0359 }
0360 
0361 KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/, const KDevelop::VcsRevision& /*limit*/)
0362 {
0363     QFileInfo curFile(localLocation.toLocalFile());
0364     if (curFile.isDir()) {
0365         KMessageBox::error(nullptr, i18n("Please select a file for this operation"));
0366         return errorsFound(i18n("Directory not supported for this operation"));
0367     }
0368 
0369     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
0370     setEnvironmentForJob(job, curFile);
0371     *job << m_perforceExecutable << "filelog" << "-lit" << localLocation;
0372 
0373     connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4LogOutput);
0374     return job;
0375 }
0376 
0377 KDevelop::VcsJob* PerforcePlugin::annotate(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/)
0378 {
0379     QFileInfo curFile(localLocation.toLocalFile());
0380     if (curFile.isDir()) {
0381         KMessageBox::error(nullptr, i18n("Please select a file for this operation"));
0382         return errorsFound(i18n("Directory not supported for this operation"));
0383     }
0384 
0385     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
0386     setEnvironmentForJob(job, curFile);
0387     *job << m_perforceExecutable << "annotate" << "-qi" << localLocation;
0388 
0389     connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4AnnotateOutput);
0390     return job;
0391 }
0392 
0393 KDevelop::VcsJob* PerforcePlugin::resolve(const QList<QUrl>& /*localLocations*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
0394 {
0395     return nullptr;
0396 }
0397 
0398 KDevelop::VcsJob* PerforcePlugin::createWorkingCopy(const KDevelop::VcsLocation& /*sourceRepository*/, const QUrl& /*destinationDirectory*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
0399 {
0400     return nullptr;
0401 }
0402 
0403 KDevelop::VcsLocationWidget* PerforcePlugin::vcsLocation(QWidget* parent) const
0404 {
0405     return new StandardVcsLocationWidget(parent);
0406 }
0407 
0408 
0409 KDevelop::VcsJob* PerforcePlugin::edit(const QList<QUrl>& localLocations)
0410 {
0411     QFileInfo curFile(localLocations.front().toLocalFile());
0412 
0413     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
0414     setEnvironmentForJob(job, curFile);
0415     *job << m_perforceExecutable << "edit" << localLocations;
0416 
0417     return job;
0418 }
0419 
0420 KDevelop::VcsJob* PerforcePlugin::edit(const QUrl& /*localLocation*/)
0421 {
0422     return nullptr;
0423 }
0424 
0425 KDevelop::VcsJob* PerforcePlugin::unedit(const QUrl& /*localLocation*/)
0426 {
0427     return nullptr;
0428 }
0429 
0430 KDevelop::VcsJob* PerforcePlugin::localRevision(const QUrl& /*localLocation*/, KDevelop::VcsRevision::RevisionType)
0431 {
0432     return nullptr;
0433 }
0434 
0435 KDevelop::VcsJob* PerforcePlugin::import(const QString& /*commitMessage*/, const QUrl& /*sourceDirectory*/, const KDevelop::VcsLocation& /*destinationRepository*/)
0436 {
0437     return nullptr;
0438 }
0439 
0440 KDevelop::ContextMenuExtension PerforcePlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent)
0441 {
0442     m_common->setupFromContext(context);
0443 
0444     const QList<QUrl> & ctxUrlList  = m_common->contextUrlList();
0445 
0446     bool hasVersionControlledEntries = false;
0447     for( const QUrl& url : ctxUrlList) {
0448         if (isValidDirectory(url)) {
0449             hasVersionControlledEntries = true;
0450             break;
0451         }
0452     }
0453 
0454     if (!hasVersionControlledEntries)
0455         return IPlugin::contextMenuExtension(context, parent);
0456 
0457     QMenu * perforceMenu = m_common->commonActions(parent);
0458     perforceMenu->addSeparator();
0459 
0460     perforceMenu->addSeparator();
0461     if (!m_edit_action) {
0462          m_edit_action = new QAction(i18nc("@action::inmenu", "Edit"), this);
0463          connect(m_edit_action, &QAction::triggered, this, & PerforcePlugin::ctxEdit);
0464      }
0465      perforceMenu->addAction(m_edit_action);
0466 
0467     ContextMenuExtension menuExt;
0468     menuExt.addAction(ContextMenuExtension::VcsGroup, perforceMenu->menuAction());
0469 
0470     return menuExt;
0471 }
0472 
0473 void PerforcePlugin::ctxEdit()
0474 {
0475     QList<QUrl> const & ctxUrlList = m_common->contextUrlList();
0476     KDevelop::ICore::self()->runController()->registerJob(edit(ctxUrlList));
0477 }
0478 
0479 void PerforcePlugin::setEnvironmentForJob(DVcsJob* job, const QFileInfo& curFile)
0480 {    
0481     KProcess* jobproc = job->process();
0482     jobproc->setEnv(QStringLiteral("P4CONFIG"), m_perforceConfigName);
0483     if (curFile.isDir()) {
0484         jobproc->setEnv(QStringLiteral("PWD"), curFile.filePath());
0485     } else {
0486         jobproc->setEnv(QStringLiteral("PWD"), curFile.absolutePath());        
0487     }
0488 }
0489 
0490 QList<QVariant> PerforcePlugin::getQvariantFromLogOutput(QStringList const& outputLines)
0491 {
0492     const QString LOGENTRY_START(QStringLiteral("... #"));
0493     const QString DEPOTMESSAGE_START(QStringLiteral("... ."));
0494     QMap<int, VcsEvent> changes;
0495     QList<QVariant> commits;
0496     QString currentFileName;
0497     QString changeNumberStr, author,changeDescription, commitMessage;
0498     VcsEvent currentVcsEvent;
0499     VcsItemEvent currentRepoFile;
0500     VcsRevision rev;
0501     int indexofAt;
0502     int changeNumber = 0;
0503     
0504     for (const QString& line : outputLines) {
0505         if (!line.startsWith(LOGENTRY_START) && !line.startsWith(DEPOTMESSAGE_START)
0506             && !line.startsWith(QLatin1Char('\t'))) {
0507             currentFileName = line;
0508         }
0509         if(line.indexOf(LOGENTRY_START) != -1)
0510         {
0511             // expecting the Logentry line to be of the form:
0512             //... #5 change 10 edit on 2010/12/06 12:07:31 by mvo@testbed (text)
0513             changeNumberStr = line.section(QLatin1Char(' '), 3, 3); // We use global change number
0514             changeNumber = changeNumberStr.toInt();
0515             author = line.section(QLatin1Char(' '), 9, 9);
0516             changeDescription = line.section(QLatin1Char(' '), 4, 4);
0517             indexofAt = author.indexOf(QLatin1Char('@'));
0518             author.remove(indexofAt, author.size()); // Only keep the username itself
0519             rev.setRevisionValue(changeNumberStr, KDevelop::VcsRevision::GlobalNumber);
0520             
0521             changes[changeNumber].setRevision(rev);
0522             changes[changeNumber].setAuthor(author);
0523             changes[changeNumber].setDate(
0524                 QDateTime::fromString(line.section(QLatin1Char(' '), 6, 7), QStringLiteral("yyyy/MM/dd hh:mm:ss")));
0525             currentRepoFile.setRepositoryLocation(currentFileName);
0526             currentRepoFile.setActions( actionsFromString(changeDescription) );
0527             changes[changeNumber].addItem(currentRepoFile);
0528             commitMessage.clear(); // We have a new entry, clear message
0529         }
0530         if (line.startsWith(QLatin1Char('\t')) || line.startsWith(DEPOTMESSAGE_START)) {
0531             commitMessage += line.trimmed() + QLatin1Char('\n');
0532             changes[changeNumber].setMessage(commitMessage);
0533         }
0534     }
0535     
0536     for(const auto& item : qAsConst(changes)) {
0537         commits.prepend(QVariant::fromValue(item));
0538     }
0539     return commits;
0540 }
0541 
0542 void PerforcePlugin::parseP4StatusOutput(DVcsJob* job)
0543 {
0544     const QStringList outputLines = job->output().split(QLatin1Char('\n'), Qt::SkipEmptyParts);
0545     QVariantList statuses;
0546     const QString ACTION_STR(QStringLiteral("... action "));
0547     const QString CLIENT_FILE_STR(QStringLiteral("... clientFile "));
0548 
0549 
0550 
0551     VcsStatusInfo status;
0552     status.setState(VcsStatusInfo::ItemUserState);
0553     for (const QString& line : outputLines) {
0554         int idx(line.indexOf(ACTION_STR));
0555         if (idx != -1) {
0556             QString curr = line.mid(ACTION_STR.size());
0557 
0558             if (curr == QLatin1String("edit")) {
0559                 status.setState(VcsStatusInfo::ItemModified);
0560             } else if (curr == QLatin1String("add")) {
0561                 status.setState(VcsStatusInfo::ItemAdded);
0562             } else {
0563                 status.setState(VcsStatusInfo::ItemUserState);
0564             }
0565             continue;
0566         }
0567         idx = line.indexOf(CLIENT_FILE_STR);
0568         if (idx != -1) {
0569             QUrl fileUrl = QUrl::fromLocalFile(line.mid(CLIENT_FILE_STR.size()));
0570 
0571             status.setUrl(fileUrl);
0572         }
0573     }
0574     statuses.append(QVariant::fromValue<VcsStatusInfo>(status));
0575     job->setResults(statuses);
0576 }
0577 
0578 void PerforcePlugin::parseP4LogOutput(KDevelop::DVcsJob* job)
0579 {
0580     QList<QVariant> commits(getQvariantFromLogOutput(job->output().split(QLatin1Char('\n'), Qt::SkipEmptyParts)));
0581 
0582     job->setResults(commits);
0583 }
0584 
0585 void PerforcePlugin::parseP4DiffOutput(DVcsJob* job)
0586 {
0587     VcsDiff diff;
0588     diff.setDiff(job->output());
0589     QDir dir(job->directory());
0590 
0591     do {
0592         if (dir.exists(m_perforceConfigName)) {
0593             break;
0594         }
0595     } while (dir.cdUp());
0596 
0597     diff.setBaseDiff(QUrl::fromLocalFile(dir.absolutePath()));
0598 
0599     job->setResults(QVariant::fromValue(diff));
0600 }
0601 
0602 void PerforcePlugin::parseP4AnnotateOutput(DVcsJob *job)
0603 {
0604     QVariantList results;
0605     /// First get the changelists for this file
0606     QStringList strList(job->dvcsCommand());
0607     QString localLocation(strList.last()); /// ASSUMPTION WARNING - localLocation is the last in the annotate command
0608     KDevelop::VcsRevision dummyRev;
0609     QScopedPointer<DVcsJob> logJob(new DVcsJob(job->directory(), this, OutputJob::Silent));
0610     QFileInfo curFile(localLocation);
0611     setEnvironmentForJob(logJob.data(), curFile);
0612     *logJob << m_perforceExecutable << "filelog" << "-lit" << localLocation;
0613 
0614     QList<QVariant> commits;
0615     if (logJob->exec() && logJob->status() == KDevelop::VcsJob::JobSucceeded) {
0616         if (!job->output().isEmpty()) {
0617             commits = getQvariantFromLogOutput(logJob->output().split(QLatin1Char('\n'), Qt::SkipEmptyParts));
0618         }
0619     }
0620     
0621     VcsEvent item;
0622     QMap<qlonglong, VcsEvent> globalCommits;
0623     /// Move the VcsEvents to a more suitable data structure
0624     for (auto& commit : qAsConst(commits)) {
0625         if (commit.canConvert<VcsEvent>()) {
0626             item = commit.value<VcsEvent>();
0627         }
0628         globalCommits.insert(item.revision().revisionValue().toLongLong(), item);
0629     }
0630 
0631     const QStringList lines = job->output().split(QLatin1Char('\n'));
0632 
0633     int lineNumber = 0;
0634     QMap<qlonglong, VcsEvent>::iterator currentEvent;
0635     bool convertToIntOk(false);
0636     int globalRevisionInt(0);
0637     QString globalRevision;
0638     for (auto& line : lines) {
0639         if (line.isEmpty()) {
0640             continue;
0641         }
0642 
0643         globalRevision = line.left(line.indexOf(QLatin1Char(':')));
0644 
0645         VcsAnnotationLine annotation;
0646         annotation.setLineNumber(lineNumber);
0647         VcsRevision rev;
0648         rev.setRevisionValue(globalRevision, KDevelop::VcsRevision::GlobalNumber);
0649         annotation.setRevision(rev);
0650         // Find the other info in the commits list
0651         globalRevisionInt = globalRevision.toLongLong(&convertToIntOk);
0652         if(convertToIntOk)
0653         {
0654             currentEvent = globalCommits.find(globalRevisionInt);
0655             annotation.setAuthor(currentEvent->author());
0656             annotation.setCommitMessage(currentEvent->message());
0657             annotation.setDate(currentEvent->date());
0658         }
0659 
0660         results += QVariant::fromValue(annotation);
0661         ++lineNumber;
0662     }
0663     
0664     job->setResults(results);
0665 }
0666 
0667 
0668 KDevelop::VcsJob* PerforcePlugin::errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity)
0669 {
0670     auto* j = new DVcsJob(QDir::temp(), this, verbosity);
0671     *j << "echo" << i18n("error: %1", error) << "-n";
0672     return j;
0673 }
0674 
0675 #include "moc_perforceplugin.cpp"