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