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