1 /*
2     SPDX-FileCopyrightText: 2010 Morten Danielsen Volden <mvolden2@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "perforceplugin.h"
8 #include "ui/perforceimportmetadatawidget.h"
9 #include "debug.h"
10 
11 #include <KLocalizedString>
12 #include <QFileInfo>
13 #include <QDateTime>
14 #include <QDir>
15 #include <QProcessEnvironment>
16 #include <QMenu>
17 
18 #include <KMessageBox>
19 #include <vcs/vcsjob.h>
20 #include <vcs/vcsrevision.h>
21 #include <vcs/vcsstatusinfo.h>
22 #include <vcs/vcsevent.h>
23 #include <vcs/dvcs/dvcsjob.h>
24 #include <vcs/vcsannotation.h>
25 #include <vcs/widgets/standardvcslocationwidget.h>
26 
27 #include <interfaces/context.h>
28 #include <interfaces/contextmenuextension.h>
29 #include <interfaces/icore.h>
30 #include <interfaces/iruncontroller.h>
31 
32 #include <vcs/vcspluginhelper.h>
33 
34 using namespace KDevelop;
35 
36 namespace
37 {
toRevisionName(const KDevelop::VcsRevision & rev,const QString & currentRevision=QString ())38 QString toRevisionName(const KDevelop::VcsRevision& rev, const QString& currentRevision=QString())
39 {
40     bool ok;
41     int previous = currentRevision.toInt(&ok);
42     previous--;
43     QString tmp;
44     switch(rev.revisionType()) {
45         case VcsRevision::Special:
46             switch(rev.revisionValue().value<VcsRevision::RevisionSpecialType>()) {
47                 case VcsRevision::Head:
48                     return QStringLiteral("#head");
49                 case VcsRevision::Base:
50                     return QStringLiteral("#have");
51                 case VcsRevision::Working:
52                     return QStringLiteral("#have");
53                 case VcsRevision::Previous:
54                     Q_ASSERT(!currentRevision.isEmpty());
55                     tmp.setNum(previous);
56                     tmp.prepend(QLatin1Char('#'));
57                     return tmp;
58                 case VcsRevision::Start:
59                     return QString();
60                 case VcsRevision::UserSpecialType: //Not used
61                     Q_ASSERT(false && "i don't know how to do that");
62             }
63             break;
64         case VcsRevision::GlobalNumber:
65             tmp.append(QLatin1Char('#') + rev.revisionValue().toString());
66             return tmp;
67         case VcsRevision::Date:
68         case VcsRevision::FileNumber:
69         case VcsRevision::Invalid:
70         case VcsRevision::UserType:
71             Q_ASSERT(false);
72     }
73     return QString();
74 }
75 
76 
actionsFromString(QString const & changeDescription)77 VcsItemEvent::Actions actionsFromString(QString const& changeDescription)
78 {
79     if(changeDescription == QLatin1String("add"))
80         return VcsItemEvent::Added;
81     if(changeDescription == QLatin1String("delete"))
82         return VcsItemEvent::Deleted;
83     return VcsItemEvent::Modified;
84 }
85 
urlDir(const QUrl & url)86 QDir urlDir(const QUrl& url)
87 {
88     QFileInfo f(url.toLocalFile());
89     if(f.isDir())
90         return QDir(url.toLocalFile());
91     else
92         return f.absoluteDir();
93 }
94 
95 }
96 
PerforcePlugin(QObject * parent,const QVariantList &)97 PerforcePlugin::PerforcePlugin(QObject* parent, const QVariantList&):
98     KDevelop::IPlugin(QStringLiteral("kdevperforce"), parent)
99     , m_common(new KDevelop::VcsPluginHelper(this, this))
100     , m_perforceConfigName(QStringLiteral("p4config.txt"))
101     , m_perforceExecutable(QStringLiteral("p4"))
102     , m_edit_action(nullptr)
103 {
104     QProcessEnvironment currentEviron(QProcessEnvironment::systemEnvironment());
105     QString tmp(currentEviron.value(QStringLiteral("P4CONFIG")));
106     if (tmp.isEmpty()) {
107         // We require the P4CONFIG variable to be set because the perforce command line client will need it
108         setErrorDescription(i18n("The variable P4CONFIG is not set. Is perforce installed on the system?"));
109         return;
110     } else {
111         m_perforceConfigName = tmp;
112     }
113     qCDebug(PLUGIN_PERFORCE) << "The value of P4CONFIG is : " << tmp;
114 }
115 
~PerforcePlugin()116 PerforcePlugin::~PerforcePlugin()
117 {
118 }
119 
name() const120 QString PerforcePlugin::name() const
121 {
122     return i18n("Perforce");
123 }
124 
createImportMetadataWidget(QWidget * parent)125 KDevelop::VcsImportMetadataWidget* PerforcePlugin::createImportMetadataWidget(QWidget* parent)
126 {
127     return new PerforceImportMetadataWidget(parent);
128 }
129 
isValidRemoteRepositoryUrl(const QUrl & remoteLocation)130 bool PerforcePlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation)
131 {
132     Q_UNUSED(remoteLocation);
133     // TODO
134     return false;
135 }
136 
isValidDirectory(const QUrl & dirPath)137 bool PerforcePlugin::isValidDirectory(const QUrl & dirPath)
138 {
139     const QFileInfo finfo(dirPath.toLocalFile());
140     QDir dir = finfo.isDir() ? QDir(dirPath.toLocalFile()) : finfo.absoluteDir();
141 
142     do {
143         if (dir.exists(m_perforceConfigName)) {
144             return true;
145         }
146     } while (dir.cdUp());
147     return false;
148 }
149 
isVersionControlled(const QUrl & localLocation)150 bool PerforcePlugin::isVersionControlled(const QUrl& localLocation)
151 {
152     QFileInfo fsObject(localLocation.toLocalFile());
153     if (fsObject.isDir()) {
154         return isValidDirectory(localLocation);
155     }
156     return parseP4fstat(fsObject, KDevelop::OutputJob::Silent);
157 }
158 
p4fstatJob(const QFileInfo & curFile,OutputJob::OutputJobVerbosity verbosity)159 DVcsJob* PerforcePlugin::p4fstatJob(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity)
160 {
161     auto* job = new DVcsJob(curFile.absolutePath(), this, verbosity);
162     setEnvironmentForJob(job, curFile);
163     *job << m_perforceExecutable << "fstat" << curFile.fileName();
164     return job;
165 }
166 
parseP4fstat(const QFileInfo & curFile,OutputJob::OutputJobVerbosity verbosity)167 bool PerforcePlugin::parseP4fstat(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity)
168 {
169     QScopedPointer<DVcsJob> job(p4fstatJob(curFile, verbosity));
170     if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) {
171         qCDebug(PLUGIN_PERFORCE) << "Perforce returned: " << job->output();
172         if (!job->output().isEmpty())
173             return true;
174     }
175     return false;
176 }
177 
getRepositoryName(const QFileInfo & curFile)178 QString PerforcePlugin::getRepositoryName(const QFileInfo& curFile)
179 {
180     const QString DEPOT_FILE_STR(QStringLiteral("... depotFile "));
181     QString ret;
182     QScopedPointer<DVcsJob> job(p4fstatJob(curFile, KDevelop::OutputJob::Silent));
183     if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) {
184         if (!job->output().isEmpty()) {
185 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
186             const QStringList outputLines = job->output().split('\n', Qt::SkipEmptyParts);
187 #else
188             const QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts);
189 #endif
190             for (const QString& line : outputLines) {
191                 int idx(line.indexOf(DEPOT_FILE_STR));
192                 if (idx != -1) {
193                     ret = line.mid(DEPOT_FILE_STR.size());
194                     return ret;
195                 }
196             }
197         }
198     }
199 
200     return ret;
201 }
202 
repositoryLocation(const QUrl &)203 KDevelop::VcsJob* PerforcePlugin::repositoryLocation(const QUrl& /*localLocation*/)
204 {
205     return nullptr;
206 }
207 
add(const QList<QUrl> & localLocations,KDevelop::IBasicVersionControl::RecursionMode)208 KDevelop::VcsJob* PerforcePlugin::add(const QList<QUrl>& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
209 {
210     QFileInfo curFile(localLocations.front().toLocalFile());
211     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
212     setEnvironmentForJob(job, curFile);
213     *job << m_perforceExecutable << "add" << localLocations;
214 
215     return job;
216 }
217 
remove(const QList<QUrl> &)218 KDevelop::VcsJob* PerforcePlugin::remove(const QList<QUrl>& /*localLocations*/)
219 {
220     return nullptr;
221 }
222 
copy(const QUrl &,const QUrl &)223 KDevelop::VcsJob* PerforcePlugin::copy(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDstn*/)
224 {
225     return nullptr;
226 }
227 
move(const QUrl &,const QUrl &)228 KDevelop::VcsJob* PerforcePlugin::move(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDst*/)
229 {
230     return nullptr;
231 }
232 
status(const QList<QUrl> & localLocations,KDevelop::IBasicVersionControl::RecursionMode)233 KDevelop::VcsJob* PerforcePlugin::status(const QList<QUrl>& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
234 {
235     if (localLocations.count() != 1) {
236         KMessageBox::error(nullptr, i18n("Please select only one item for this operation"));
237         return nullptr;
238     }
239 
240     QFileInfo curFile(localLocations.front().toLocalFile());
241 
242     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
243     setEnvironmentForJob(job, curFile);
244     *job << m_perforceExecutable << "fstat" << curFile.fileName();
245     connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4StatusOutput);
246 
247     return job;
248 }
249 
revert(const QList<QUrl> & localLocations,KDevelop::IBasicVersionControl::RecursionMode)250 KDevelop::VcsJob* PerforcePlugin::revert(const QList<QUrl>& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
251 {
252     if (localLocations.count() != 1) {
253         KMessageBox::error(nullptr, i18n("Please select only one item for this operation"));
254         return nullptr;
255     }
256 
257     QFileInfo curFile(localLocations.front().toLocalFile());
258 
259     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
260     setEnvironmentForJob(job, curFile);
261     *job << m_perforceExecutable << "revert" << curFile.fileName();
262 
263     return job;
264 
265 }
266 
update(const QList<QUrl> & localLocations,const KDevelop::VcsRevision &,KDevelop::IBasicVersionControl::RecursionMode)267 KDevelop::VcsJob* PerforcePlugin::update(const QList<QUrl>& localLocations, const KDevelop::VcsRevision& /*rev*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
268 {
269     QFileInfo curFile(localLocations.front().toLocalFile());
270 
271     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
272     setEnvironmentForJob(job, curFile);
273     //*job << m_perforceExecutable << "-p" << "127.0.0.1:1666" << "info"; - Let's keep this for now it's very handy for debugging
274     QString fileOrDirectory;
275     if (curFile.isDir())
276         fileOrDirectory = curFile.absolutePath() + "/...";
277     else
278         fileOrDirectory = curFile.fileName();
279     *job << m_perforceExecutable << "sync" << fileOrDirectory;
280     return job;
281 }
282 
commit(const QString & message,const QList<QUrl> & localLocations,KDevelop::IBasicVersionControl::RecursionMode)283 KDevelop::VcsJob* PerforcePlugin::commit(const QString& message, const QList<QUrl>& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
284 {
285     if (localLocations.empty() || message.isEmpty())
286         return errorsFound(i18n("No files or message specified"));
287 
288 
289     QFileInfo curFile(localLocations.front().toLocalFile());
290 
291     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
292     setEnvironmentForJob(job, curFile);
293     *job << m_perforceExecutable << "submit" << "-d" << message << localLocations;
294 
295     return job;
296 }
297 
diff(const QUrl & fileOrDirectory,const KDevelop::VcsRevision & srcRevision,const KDevelop::VcsRevision & dstRevision,KDevelop::IBasicVersionControl::RecursionMode)298 KDevelop::VcsJob* PerforcePlugin::diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
299 {
300     QFileInfo curFile(fileOrDirectory.toLocalFile());
301     QString depotSrcFileName = getRepositoryName(curFile);
302     QString depotDstFileName = depotSrcFileName;
303     depotSrcFileName.append(toRevisionName(srcRevision, dstRevision.prettyValue())); // dstRevision actually contains the number that we want to take previous of
304 
305     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
306     setEnvironmentForJob(job, curFile);
307     switch (dstRevision.revisionType()) {
308     case VcsRevision::FileNumber:
309     case VcsRevision::GlobalNumber:
310         depotDstFileName.append(QLatin1Char('#') + dstRevision.prettyValue());
311         *job << m_perforceExecutable << "diff2" << "-u" << depotSrcFileName << depotDstFileName;
312         break;
313     case VcsRevision::Special:
314         switch (dstRevision.revisionValue().value<VcsRevision::RevisionSpecialType>()) {
315         case VcsRevision::Working:
316             *job << m_perforceExecutable << "diff" << "-du" << depotSrcFileName;
317             break;
318         case VcsRevision::Start:
319         case VcsRevision::UserSpecialType:
320         default:
321             break;
322         }
323         break;
324     default:
325         break;
326     }
327 
328     connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4DiffOutput);
329     return job;
330 }
331 
log(const QUrl & localLocation,const KDevelop::VcsRevision & rev,long unsigned int limit)332 KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, long unsigned int limit)
333 {
334     static QString lastSeenChangeList;
335     QFileInfo curFile(localLocation.toLocalFile());
336     QString localLocationAndRevStr = localLocation.toLocalFile();
337 
338     auto* job = new DVcsJob(urlDir(localLocation), this, KDevelop::OutputJob::Verbose);
339     setEnvironmentForJob(job, curFile);
340     *job << m_perforceExecutable << "filelog" << "-lit";
341     if(limit > 0)
342         *job << QStringLiteral("-m %1").arg(limit);
343 
344     if (curFile.isDir()) {
345         localLocationAndRevStr.append("/...");
346     }
347     QString revStr = toRevisionName(rev, QString());
348     if(!revStr.isEmpty()) {
349         // This is not too nice, but perforce argument for restricting output from filelog does not Work :-(
350         // So putting this in so we do not end up in infinite loop calling log,
351         if(revStr == lastSeenChangeList) {
352             localLocationAndRevStr.append("#none");
353             lastSeenChangeList.clear();
354         } else {
355             localLocationAndRevStr.append(revStr);
356             lastSeenChangeList = revStr;
357         }
358     }
359     *job << localLocationAndRevStr;
360     qCDebug(PLUGIN_PERFORCE) << "Issuing the following command to p4: " << job->dvcsCommand();
361     connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4LogOutput);
362     return job;
363 }
364 
log(const QUrl & localLocation,const KDevelop::VcsRevision &,const KDevelop::VcsRevision &)365 KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/, const KDevelop::VcsRevision& /*limit*/)
366 {
367     QFileInfo curFile(localLocation.toLocalFile());
368     if (curFile.isDir()) {
369         KMessageBox::error(nullptr, i18n("Please select a file for this operation"));
370         return errorsFound(i18n("Directory not supported for this operation"));
371     }
372 
373     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
374     setEnvironmentForJob(job, curFile);
375     *job << m_perforceExecutable << "filelog" << "-lit" << localLocation;
376 
377     connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4LogOutput);
378     return job;
379 }
380 
annotate(const QUrl & localLocation,const KDevelop::VcsRevision &)381 KDevelop::VcsJob* PerforcePlugin::annotate(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/)
382 {
383     QFileInfo curFile(localLocation.toLocalFile());
384     if (curFile.isDir()) {
385         KMessageBox::error(nullptr, i18n("Please select a file for this operation"));
386         return errorsFound(i18n("Directory not supported for this operation"));
387     }
388 
389     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
390     setEnvironmentForJob(job, curFile);
391     *job << m_perforceExecutable << "annotate" << "-qi" << localLocation;
392 
393     connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4AnnotateOutput);
394     return job;
395 }
396 
resolve(const QList<QUrl> &,KDevelop::IBasicVersionControl::RecursionMode)397 KDevelop::VcsJob* PerforcePlugin::resolve(const QList<QUrl>& /*localLocations*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
398 {
399     return nullptr;
400 }
401 
createWorkingCopy(const KDevelop::VcsLocation &,const QUrl &,KDevelop::IBasicVersionControl::RecursionMode)402 KDevelop::VcsJob* PerforcePlugin::createWorkingCopy(const KDevelop::VcsLocation& /*sourceRepository*/, const QUrl& /*destinationDirectory*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
403 {
404     return nullptr;
405 }
406 
vcsLocation(QWidget * parent) const407 KDevelop::VcsLocationWidget* PerforcePlugin::vcsLocation(QWidget* parent) const
408 {
409     return new StandardVcsLocationWidget(parent);
410 }
411 
412 
edit(const QList<QUrl> & localLocations)413 KDevelop::VcsJob* PerforcePlugin::edit(const QList<QUrl>& localLocations)
414 {
415     QFileInfo curFile(localLocations.front().toLocalFile());
416 
417     auto* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
418     setEnvironmentForJob(job, curFile);
419     *job << m_perforceExecutable << "edit" << localLocations;
420 
421     return job;
422 }
423 
edit(const QUrl &)424 KDevelop::VcsJob* PerforcePlugin::edit(const QUrl& /*localLocation*/)
425 {
426     return nullptr;
427 }
428 
unedit(const QUrl &)429 KDevelop::VcsJob* PerforcePlugin::unedit(const QUrl& /*localLocation*/)
430 {
431     return nullptr;
432 }
433 
localRevision(const QUrl &,KDevelop::VcsRevision::RevisionType)434 KDevelop::VcsJob* PerforcePlugin::localRevision(const QUrl& /*localLocation*/, KDevelop::VcsRevision::RevisionType)
435 {
436     return nullptr;
437 }
438 
import(const QString &,const QUrl &,const KDevelop::VcsLocation &)439 KDevelop::VcsJob* PerforcePlugin::import(const QString& /*commitMessage*/, const QUrl& /*sourceDirectory*/, const KDevelop::VcsLocation& /*destinationRepository*/)
440 {
441     return nullptr;
442 }
443 
contextMenuExtension(KDevelop::Context * context,QWidget * parent)444 KDevelop::ContextMenuExtension PerforcePlugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent)
445 {
446     m_common->setupFromContext(context);
447 
448     const QList<QUrl> & ctxUrlList  = m_common->contextUrlList();
449 
450     bool hasVersionControlledEntries = false;
451     for( const QUrl& url : ctxUrlList) {
452         if (isValidDirectory(url)) {
453             hasVersionControlledEntries = true;
454             break;
455         }
456     }
457 
458     if (!hasVersionControlledEntries)
459         return IPlugin::contextMenuExtension(context, parent);
460 
461     QMenu * perforceMenu = m_common->commonActions(parent);
462     perforceMenu->addSeparator();
463 
464     perforceMenu->addSeparator();
465     if (!m_edit_action) {
466          m_edit_action = new QAction(i18nc("@action::inmenu", "Edit"), this);
467          connect(m_edit_action, &QAction::triggered, this, & PerforcePlugin::ctxEdit);
468      }
469      perforceMenu->addAction(m_edit_action);
470 
471     ContextMenuExtension menuExt;
472     menuExt.addAction(ContextMenuExtension::VcsGroup, perforceMenu->menuAction());
473 
474     return menuExt;
475 }
476 
ctxEdit()477 void PerforcePlugin::ctxEdit()
478 {
479     QList<QUrl> const & ctxUrlList = m_common->contextUrlList();
480     KDevelop::ICore::self()->runController()->registerJob(edit(ctxUrlList));
481 }
482 
setEnvironmentForJob(DVcsJob * job,const QFileInfo & curFile)483 void PerforcePlugin::setEnvironmentForJob(DVcsJob* job, const QFileInfo& curFile)
484 {
485     KProcess* jobproc = job->process();
486     jobproc->setEnv(QStringLiteral("P4CONFIG"), m_perforceConfigName);
487     if (curFile.isDir()) {
488         jobproc->setEnv(QStringLiteral("PWD"), curFile.filePath());
489     } else {
490         jobproc->setEnv(QStringLiteral("PWD"), curFile.absolutePath());
491     }
492 }
493 
getQvariantFromLogOutput(QStringList const & outputLines)494 QList<QVariant> PerforcePlugin::getQvariantFromLogOutput(QStringList const& outputLines)
495 {
496     const QString LOGENTRY_START(QStringLiteral("... #"));
497     const QString DEPOTMESSAGE_START(QStringLiteral("... ."));
498     QMap<int, VcsEvent> changes;
499     QList<QVariant> commits;
500     QString currentFileName;
501     QString changeNumberStr, author,changeDescription, commitMessage;
502     VcsEvent currentVcsEvent;
503     VcsItemEvent currentRepoFile;
504     VcsRevision rev;
505     int indexofAt;
506     int changeNumber = 0;
507 
508     for (const QString& line : outputLines) {
509         if (!line.startsWith(LOGENTRY_START) && !line.startsWith(DEPOTMESSAGE_START)  && !line.startsWith('\t')) {
510             currentFileName = line;
511         }
512         if(line.indexOf(LOGENTRY_START) != -1)
513         {
514             // expecting the Logentry line to be of the form:
515             //... #5 change 10 edit on 2010/12/06 12:07:31 by mvo@testbed (text)
516             changeNumberStr = line.section(' ', 3, 3 ); // We use global change number
517             changeNumber = changeNumberStr.toInt();
518             author = line.section(' ', 9, 9);
519             changeDescription = line.section(' ' , 4, 4 );
520             indexofAt = author.indexOf('@');
521             author.remove(indexofAt, author.size()); // Only keep the username itself
522             rev.setRevisionValue(changeNumberStr, KDevelop::VcsRevision::GlobalNumber);
523 
524             changes[changeNumber].setRevision(rev);
525             changes[changeNumber].setAuthor(author);
526             changes[changeNumber].setDate(QDateTime::fromString(line.section(' ', 6, 7), QStringLiteral("yyyy/MM/dd hh:mm:ss")));
527             currentRepoFile.setRepositoryLocation(currentFileName);
528             currentRepoFile.setActions( actionsFromString(changeDescription) );
529             changes[changeNumber].addItem(currentRepoFile);
530             commitMessage.clear(); // We have a new entry, clear message
531         }
532         if (line.startsWith('\t') || line.startsWith(DEPOTMESSAGE_START)) {
533             commitMessage += line.trimmed() + '\n';
534             changes[changeNumber].setMessage(commitMessage);
535         }
536 
537     }
538 
539     for(const auto& item : qAsConst(changes)) {
540         commits.prepend(QVariant::fromValue(item));
541     }
542     return commits;
543 }
544 
parseP4StatusOutput(DVcsJob * job)545 void PerforcePlugin::parseP4StatusOutput(DVcsJob* job)
546 {
547 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
548     const QStringList outputLines = job->output().split('\n', Qt::SkipEmptyParts);
549 #else
550     const QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts);
551 #endif
552     QVariantList statuses;
553     const QString ACTION_STR(QStringLiteral("... action "));
554     const QString CLIENT_FILE_STR(QStringLiteral("... clientFile "));
555 
556 
557 
558     VcsStatusInfo status;
559     status.setState(VcsStatusInfo::ItemUserState);
560     for (const QString& line : outputLines) {
561         int idx(line.indexOf(ACTION_STR));
562         if (idx != -1) {
563             QString curr = line.mid(ACTION_STR.size());
564 
565             if (curr == QLatin1String("edit")) {
566                 status.setState(VcsStatusInfo::ItemModified);
567             } else if (curr == QLatin1String("add")) {
568                 status.setState(VcsStatusInfo::ItemAdded);
569             } else {
570                 status.setState(VcsStatusInfo::ItemUserState);
571             }
572             continue;
573         }
574         idx = line.indexOf(CLIENT_FILE_STR);
575         if (idx != -1) {
576             QUrl fileUrl = QUrl::fromLocalFile(line.mid(CLIENT_FILE_STR.size()));
577 
578             status.setUrl(fileUrl);
579         }
580     }
581     statuses.append(QVariant::fromValue<VcsStatusInfo>(status));
582     job->setResults(statuses);
583 }
584 
parseP4LogOutput(KDevelop::DVcsJob * job)585 void PerforcePlugin::parseP4LogOutput(KDevelop::DVcsJob* job)
586 {
587 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
588     QList<QVariant> commits(getQvariantFromLogOutput(job->output().split('\n', Qt::SkipEmptyParts)));
589 #else
590     QList<QVariant> commits(getQvariantFromLogOutput(job->output().split('\n', QString::SkipEmptyParts)));
591 #endif
592 
593     job->setResults(commits);
594 }
595 
parseP4DiffOutput(DVcsJob * job)596 void PerforcePlugin::parseP4DiffOutput(DVcsJob* job)
597 {
598     VcsDiff diff;
599     diff.setDiff(job->output());
600     QDir dir(job->directory());
601 
602     do {
603         if (dir.exists(m_perforceConfigName)) {
604             break;
605         }
606     } while (dir.cdUp());
607 
608     diff.setBaseDiff(QUrl::fromLocalFile(dir.absolutePath()));
609 
610     job->setResults(QVariant::fromValue(diff));
611 }
612 
parseP4AnnotateOutput(DVcsJob * job)613 void PerforcePlugin::parseP4AnnotateOutput(DVcsJob *job)
614 {
615     QVariantList results;
616     /// First get the changelists for this file
617     QStringList strList(job->dvcsCommand());
618     QString localLocation(strList.last()); /// ASSUMPTION WARNING - localLocation is the last in the annotate command
619     KDevelop::VcsRevision dummyRev;
620     QScopedPointer<DVcsJob> logJob(new DVcsJob(job->directory(), this, OutputJob::Silent));
621     QFileInfo curFile(localLocation);
622     setEnvironmentForJob(logJob.data(), curFile);
623     *logJob << m_perforceExecutable << "filelog" << "-lit" << localLocation;
624 
625     QList<QVariant> commits;
626     if (logJob->exec() && logJob->status() == KDevelop::VcsJob::JobSucceeded) {
627         if (!job->output().isEmpty()) {
628 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
629             commits = getQvariantFromLogOutput(logJob->output().split('\n', Qt::SkipEmptyParts));
630 #else
631             commits = getQvariantFromLogOutput(logJob->output().split('\n', QString::SkipEmptyParts));
632 #endif
633         }
634     }
635 
636     VcsEvent item;
637     QMap<qlonglong, VcsEvent> globalCommits;
638     /// Move the VcsEvents to a more suitable data structure
639     for (auto& commit : qAsConst(commits)) {
640         if (commit.canConvert<VcsEvent>()) {
641             item = commit.value<VcsEvent>();
642         }
643         globalCommits.insert(item.revision().revisionValue().toLongLong(), item);
644     }
645 
646     const QStringList lines = job->output().split('\n');
647 
648     int lineNumber = 0;
649     QMap<qlonglong, VcsEvent>::iterator currentEvent;
650     bool convertToIntOk(false);
651     int globalRevisionInt(0);
652     QString globalRevision;
653     for (auto& line : lines) {
654         if (line.isEmpty()) {
655             continue;
656         }
657 
658         globalRevision = line.left(line.indexOf(':'));
659 
660         VcsAnnotationLine annotation;
661         annotation.setLineNumber(lineNumber);
662         VcsRevision rev;
663         rev.setRevisionValue(globalRevision, KDevelop::VcsRevision::GlobalNumber);
664         annotation.setRevision(rev);
665         // Find the other info in the commits list
666         globalRevisionInt = globalRevision.toLongLong(&convertToIntOk);
667         if(convertToIntOk)
668         {
669             currentEvent = globalCommits.find(globalRevisionInt);
670             annotation.setAuthor(currentEvent->author());
671             annotation.setCommitMessage(currentEvent->message());
672             annotation.setDate(currentEvent->date());
673         }
674 
675         results += QVariant::fromValue(annotation);
676         ++lineNumber;
677     }
678 
679     job->setResults(results);
680 }
681 
682 
errorsFound(const QString & error,KDevelop::OutputJob::OutputJobVerbosity verbosity)683 KDevelop::VcsJob* PerforcePlugin::errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity)
684 {
685     auto* j = new DVcsJob(QDir::temp(), this, verbosity);
686     *j << "echo" << i18n("error: %1", error) << "-n";
687     return j;
688 }
689 
690 
691