diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -20,6 +20,7 @@
add_subdirectory(grepview)
add_subdirectory(git)
add_subdirectory(bazaar)
+add_subdirectory(perforce)
add_subdirectory(vcschangesview)
if (Grantlee5_FOUND)
add_subdirectory(filetemplates)
diff --git a/plugins/perforce/CMakeLists.txt b/plugins/perforce/CMakeLists.txt
new file mode 100644
--- /dev/null
+++ b/plugins/perforce/CMakeLists.txt
@@ -0,0 +1,14 @@
+add_subdirectory(p4clientstub)
+add_subdirectory(test)
+
+set(kdevperforce_PART_SRCS
+ perforceplugin.cpp
+ perforcepluginmetadata.cpp
+)
+
+kdevplatform_add_plugin(kdevperforce JSON kdevperforce.json SOURCES ${kdevperforce_PART_SRCS})
+
+target_link_libraries(kdevperforce
+ KDev::Interfaces
+ KDev::Vcs
+)
diff --git a/plugins/perforce/debug.h b/plugins/perforce/debug.h
new file mode 100644
--- /dev/null
+++ b/plugins/perforce/debug.h
@@ -0,0 +1,23 @@
+/***************************************************************************
+ * Copyright 2010 Morten Danielsen Volden *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ ***************************************************************************/
+#ifndef PERFORCE_PLUGIN_DEBUG_H
+#define PERFORCE_PLUGIN_DEBUG_H
+
+#include
+Q_DECLARE_LOGGING_CATEGORY(PLUGIN_PERFORCE)
+
+#endif
diff --git a/plugins/perforce/kdevperforce.json b/plugins/perforce/kdevperforce.json
new file mode 100644
--- /dev/null
+++ b/plugins/perforce/kdevperforce.json
@@ -0,0 +1,105 @@
+{
+ "GenericName": "Perforce VCS",
+ "GenericName[bg]": "Perforce VCS",
+ "GenericName[bs]": "Perforce VCS",
+ "GenericName[cs]": "Perforce VCS",
+ "GenericName[da]": "Perforce VCS",
+ "GenericName[de]": "Perforce VCS",
+ "GenericName[en_GB]": "Perforce VCS",
+ "GenericName[es]": "SCV Perforce",
+ "GenericName[et]": "Perforce'i versioonihaldussüsteem",
+ "GenericName[fi]": "Perforce-versionhallintajärjestelmä",
+ "GenericName[fr]": "VCS Perforce",
+ "GenericName[ga]": "Córas Rialaithe Leaganacha Perforce",
+ "GenericName[gl]": "Perforce VCS",
+ "GenericName[hu]": "Perforce VCS",
+ "GenericName[ko]": "Perforce VCS",
+ "GenericName[mr]": "परफोर्स व्हीसीएस",
+ "GenericName[nl]": "Perforce VCS",
+ "GenericName[pl]": "Perforce VCS",
+ "GenericName[pt]": "SCV Perforce",
+ "GenericName[pt_BR]": "VCS Perforce",
+ "GenericName[ro]": "Perforce VCS",
+ "GenericName[sk]": "Perforce VCS",
+ "GenericName[sv]": "Perforce versionskontrollsystem",
+ "GenericName[tr]": "Perforce VCS",
+ "GenericName[uk]": "СКВ Perforce",
+ "GenericName[x-test]": "xxPerforce VCSxx",
+ "GenericName[zh_CN]": "Perforce 版本控制系统",
+ "GenericName[zh_TW]": "Perforce VCS",
+ "KPlugin": {
+ "Authors": [
+ {
+ "Name": "Morten Danielsen Volden"
+ }
+ ],
+ "Category": "Version Control",
+ "Description": "Provides Integration with Perforce Version Control System",
+ "Description[bg]": "Поддръжка на системата за управление на версии Perforce",
+ "Description[bs]": "Pruža integraciju Perforce sistemom kontrole verzija",
+ "Description[da]": "Giver integration med versionsstyringssystemet Perforce",
+ "Description[de]": "Stellt Integration mit dem Versionsverwaltungssystem Perforce zur Verfügung",
+ "Description[en_GB]": "Provides Integration with Perforce Version Control System",
+ "Description[es]": "Proporciona integración con el sistema de control de versiones Perforce",
+ "Description[et]": "Lõimimine versioonihaldussüsteemiga Perforce",
+ "Description[fi]": "Lisää integraation Perforce-versionhallintajärjestelmälle",
+ "Description[fr]": "Permet l'intégration avec le système de contrôle de versions Perforce",
+ "Description[gl]": "Fornece integración co sistema de control de versións Perforce",
+ "Description[hu]": "Integráció a Perforce verziókezelő rendszerrel",
+ "Description[ko]": "Perforce 버전 관리 시스템 통합 제공",
+ "Description[mr]": "परफोर्स आवृत्ती नियंत्रण प्रणाली बरोबर एकीकरण पुरवितो",
+ "Description[nl]": "Biedt ondersteuning voor Perforce versiecontrolesysteem",
+ "Description[pl]": "Dostarcza integrację z systemem kontroli wersji Perforce",
+ "Description[pt]": "Fornece a integração com o sistema de controlo de versões Perforce",
+ "Description[pt_BR]": "Fornece a integração com o sistema de controle de versões Perforce",
+ "Description[ro]": "Furnizează integrare cu Sistemul de Control al Versiunii Perforce",
+ "Description[sk]": "Poskytuje integráciu s verzionovacím systémom Perforce",
+ "Description[sv]": "Tillhandahåller integrering med Perforce versionskontrollsystem",
+ "Description[tr]": "Perforce Sürüm Denetim Sistemi ile Bütünleşme Sağlar",
+ "Description[uk]": "Забезпечує інтеграцію з системою керування версіями Perforce",
+ "Description[x-test]": "xxProvides Integration with Perforce Version Control Systemxx",
+ "Description[zh_CN]": "提供与 Perforce 版本控制系统的整合",
+ "Description[zh_TW]": "提供與 Perforce 版本控制系統的整合",
+ "Icon": "kdevelop",
+ "Id": "kdevperforce",
+ "License": "GPL",
+ "Name": "Perforce Support",
+ "Name[bg]": "Поддръжка на Perforce",
+ "Name[bs]": "Perforce podrška",
+ "Name[cs]": "Podpora Perforce",
+ "Name[da]": "Perforce-understøttelse",
+ "Name[de]": "Perforce-Unterstützung",
+ "Name[en_GB]": "Perforce Support",
+ "Name[es]": "Implementación de Perforce",
+ "Name[et]": "Perforce'i toetus",
+ "Name[fi]": "Perforce-tuki",
+ "Name[fr]": "Prise en charge de Perforce",
+ "Name[ga]": "Tacaíocht Perforce",
+ "Name[gl]": "Compatibilidade con Perforce",
+ "Name[hu]": "Perforce támogatás",
+ "Name[ko]": "Perforce 지원",
+ "Name[mr]": "परफोर्स समर्थन",
+ "Name[nds]": "Perforce-Ünnerstütten",
+ "Name[nl]": "Ondersteuning van Perforce",
+ "Name[pl]": "Obsługa Perforce",
+ "Name[pt]": "Suporte para Perforce",
+ "Name[pt_BR]": "Suporte para Perforce",
+ "Name[ro]": "Suport pentru Perforce",
+ "Name[sk]": "Podpora Perforce",
+ "Name[sv]": "Stöd för Perforce",
+ "Name[tr]": "Perforce Desteği",
+ "Name[uk]": "Підтримка Perforce",
+ "Name[x-test]": "xxPerforce Supportxx",
+ "Name[zh_CN]": "Perforce 支持",
+ "Name[zh_TW]": "Perforce 支援",
+ "ServiceTypes": [
+ "KDevelop/Plugin"
+ ],
+ "Version": "5.0"
+ },
+ "X-KDevelop-Category": "Global",
+ "X-KDevelop-Mode": "GUI",
+ "X-KDevelop-IRequired": [
+ "org.kdevelop.IExecutePlugin"
+ ]
+}
diff --git a/plugins/perforce/p4clientstub/CMakeLists.txt b/plugins/perforce/p4clientstub/CMakeLists.txt
new file mode 100644
--- /dev/null
+++ b/plugins/perforce/p4clientstub/CMakeLists.txt
@@ -0,0 +1,9 @@
+project(p4clientstub)
+
+set(p4clientstub_SRCS main.cpp )
+
+add_executable(p4clientstub ${p4clientstub_SRCS} )
+
+target_link_libraries(p4clientstub
+ Qt5::Core
+)
diff --git a/plugins/perforce/p4clientstub/main.cpp b/plugins/perforce/p4clientstub/main.cpp
new file mode 100644
--- /dev/null
+++ b/plugins/perforce/p4clientstub/main.cpp
@@ -0,0 +1,243 @@
+/***************************************************************************
+ * Copyright 2013 Morten Danielsen Volden *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ ***************************************************************************/
+
+#include
+#include
+#include
+
+bool validateNumberOfArguments(int argc, char** /*argv*/)
+{
+ if (argc < 3) {
+ return false;
+ }
+
+ return true;
+}
+
+
+int fakeFstatOutput(QString const& filepath)
+{
+ QByteArray tmp = filepath.toUtf8();
+ std::string fileText(tmp.constData(), tmp.size());
+ std::cout << "... depotFile /" << fileText << std::endl;// /tools/test/new_file.txt"
+ std::cout << "... clientFile /home/projects" << fileText << std::endl;
+ std::cout << "... isMapped" << std::endl;
+ std::cout << "... headAction add" << std::endl;
+ std::cout << "... headType text" << std::endl;
+ std::cout << "... headTime 1285014691" << std::endl;
+ std::cout << "... headRev 1" << std::endl;
+ std::cout << "... headChange 759253" << std::endl;
+ std::cout << "... headModTime 1285014680" << std::endl;
+ std::cout << "... haveRev 1" << std::endl;
+ return 0;
+}
+
+int fakeRevertOutput()
+{
+ return 0;
+}
+
+int fakeSyncOutput()
+{
+ return 0;
+}
+
+int fakeSubmitOutput()
+{
+ return 0;
+}
+
+int fakeDiff2Output()
+{
+ return 0;
+}
+
+int fakeDiffOutput()
+{
+ return 0;
+}
+
+int fakeFileLogOutput(QString const& filepath)
+{
+ QByteArray tmp = filepath.toUtf8();
+ std::string fileText(tmp.constData(), tmp.size());
+ std::cout << "//depot/" << fileText << std::endl;
+ std::cout << "... #14 change 72 edit on 2013/02/04 15:38:35 by mvo@testbed (ktext)" << std::endl;
+ std::cout << std::endl;
+ std::cout << " Log a little more out" << std::endl;
+ std::cout << std::endl;
+ std::cout << "... #13 change 67 edit on 2013/02/04 15:31:48 by mov@testbed (ktext)" << std::endl;
+ std::cout << std::endl;
+ std::cout << " Rename rand -> random_number" << std::endl;
+ std::cout << std::endl;
+ std::cout << "... #12 change 79 edit on 2013/01/29 18:02:33 by mvo@testbed (ktext)" << std::endl;
+ std::cout << std::endl;
+ std::cout << " Prefer the use of std::string" << std::endl;
+ std::cout << std::endl;
+ std::cout << "... #11 change 33 edit on 2013/01/29 15:48:41 by mvo@testbed (ktext)" << std::endl;
+ std::cout << std::endl;
+ std::cout << " Correct Test Class to use QString" << std::endl;
+ std::cout << std::endl;
+ std::cout << "... #10 change 76 edit on 2013/01/28 19:21:33 by vom@testbed (ktext)" << std::endl;
+ std::cout << std::endl;
+ std::cout << " Minor changes. Changed the log level of a couple of log lines." << std::endl;
+ std::cout << std::endl;
+ std::cout << "... #9 change 17 edit on 2013/01/22 15:41:16 by vom@testbed (ktext)" << std::endl;
+ std::cout << std::endl;
+ std::cout << " Just added a comment" << std::endl;
+ std::cout << std::endl;
+ std::cout << "... #8 change 97 edit on 2013/01/17 13:54:26 by mvo@testbed (ktext)" << std::endl;
+ std::cout << std::endl;
+ std::cout << " Do not cleanup before we are absolutely done" << std::endl;
+ std::cout << std::endl;
+ std::cout << "... #7 change 12 edit on 2013/01/17 11:39:25 by mvo@testbed (ktext)" << std::endl;
+ std::cout << std::endl;
+ std::cout << " Insert a little debugging in the test class. Be more defensive when dealing with iterators" << std::endl;
+ std::cout << std::endl;
+ std::cout << "... #6 change 60 edit on 2013/01/09 13:19:07 by vom@testbed (ktext)" << std::endl;
+ std::cout << std::endl;
+ std::cout << " error and abort handling added to RingBuffer." << std::endl;
+ std::cout << std::endl;
+ std::cout << "... #5 change 30 edit on 2013/01/08 19:21:42 by mvo@testbed (ktext)" << std::endl;
+ std::cout << std::endl;
+ std::cout << " Fix two things in cleanup. Fix item on the todo list, cleanup a map" << std::endl;
+ std::cout << std::endl;
+ std::cout << "... #4 change 27 edit on 2013/01/08 13:26:05 by vom@testbed (ktext)" << std::endl;
+ std::cout << std::endl;
+ std::cout << " Clean-up of persisted data on disk has been added" << std::endl;
+ std::cout << std::endl;
+ std::cout << "... #3 change 5 edit on 2012/12/28 13:04:16 by vom@testbed (ktext)" << std::endl;
+ std::cout << std::endl;
+ std::cout << " Support for multiple things added." << std::endl;
+ std::cout << std::endl;
+ std::cout << "... #2 change 6 edit on 2012/12/04 14:05:10 by mvo@testbed (ktext)" << std::endl;
+ std::cout << std::endl;
+ std::cout << " Make Other test compile with new test class" << std::endl;
+ std::cout << std::endl;
+ std::cout << "... #1 change 1 add on 2012/12/04 13:48:25 by mvo@testbed (ktext)" << std::endl;
+ std::cout << std::endl;
+ std::cout << " Add RingBuffer" << std::endl;
+ std::cout << std::endl;
+ return 0;
+}
+
+int fakeAnnotateOutput()
+{
+ std::cout << "1: #include \\\"RingBuffer.h\\\"" << std::endl;
+ std::cout << "1: #include \\\"inc/test/someClass.h\\\"" << std::endl;
+ std::cout << "1: //#include \\\"hejsa.h\\\"" << std::endl;
+ std::cout << "1: " << std::endl;
+ std::cout << "1: #include " << std::endl;
+ std::cout << "1: " << std::endl;
+ std::cout << "1: #include " << std::endl;
+ std::cout << "1: #include " << std::endl;
+ std::cout << "1: " << std::endl;
+ std::cout << "1: #include " << std::endl;
+ std::cout << "1: " << std::endl;
+ std::cout << "1: #include " << std::endl;
+ std::cout << "1: #include " << std::endl;
+ std::cout << "1: " << std::endl;
+ std::cout << "1: " << std::endl;
+ std::cout << "5: void fillBuffer( RingBuffer& myBuffer, std::vector& testVector )" << std::endl;
+ std::cout << "1: {" << std::endl;
+ std::cout << "1: uint64_t random_range( std::numeric_limits::max() );" << std::endl;
+ std::cout << "12: uint64_t random_number ( 0 );" << std::endl;
+ std::cout << "79: /// First fill the buffer" << std::endl;
+ std::cout << "1: for ( size_t i =0; i < RINGBUFFER_SIZE; ++i )" << std::endl;
+ std::cout << "1: {" << std::endl;
+ std::cout << "5: random_number = std::rand() % random_range;" << std::endl;
+ std::cout << "5: myBuffer.push_back ( random_number );" << std::endl;
+ std::cout << "5: testVector.push_back ( random_number );" << std::endl;
+ std::cout << "6: }" << std::endl;
+ std::cout << "76: }" << std::endl;
+ std::cout << "76: " << std::endl;
+ std::cout << "17: void testWithOnlyknown( RingBuffer& myBuffer, std::vector& testVector )" << std::endl;
+ std::cout << "17: {" << std::endl;
+ std::cout << "17: uint64_t random_range ( RINGBUFFER_SIZE );" << std::endl;
+ std::cout << "97: uint64_t random_number ( 0 );" << std::endl;
+ std::cout << "12: /// Perform the test run on values we have already inserted." << std::endl;
+ std::cout << "1: for ( size_t j =0; j < 1000000; ++j )" << std::endl;
+ std::cout << "1: {" << std::endl;
+ std::cout << "1: random_number = std::rand() % random_range;" << std::endl;
+ std::cout << "1: myBuffer.push_back ( testVector[random_number] );" << std::endl;
+ std::cout << "1: }" << std::endl;
+ std::cout << "67: " << std::endl;
+ std::cout << "67: }" << std::endl;
+ std::cout << "67: " << std::endl;
+ std::cout << "60: int main( int argc, char **argv )" << std::endl;
+ std::cout << "60: {" << std::endl;
+ std::cout << "27: RingBuffer myBuffer;" << std::endl;
+ std::cout << "1: std::vector testVector;" << std::endl;
+ std::cout << "1: " << std::endl;
+ std::cout << "1: std::srand( std::time ( 0 ) );" << std::endl;
+ std::cout << "1: " << std::endl;
+ std::cout << "1: fillBuffer( myBuffer, testVector );" << std::endl;
+ std::cout << "27: testWithOnlyknown( myBuffer, testVector );" << std::endl;
+ std::cout << "27: " << std::endl;
+ std::cout << "27: std::cout << \\\"Done Testing\\\" << std::endl;" << std::endl;
+ std::cout << "1: " << std::endl;
+ std::cout << "1: return 0;" << std::endl;
+ std::cout << "1: }" << std::endl;
+ return 0;
+}
+
+int fakeEditOutput(QString const& filepath)
+{
+ QByteArray tmp = filepath.toUtf8();
+ std::string fileText(tmp.constData(), tmp.size());
+ std::cout << fileText << "#1 - opened for edit" << std::endl;
+ return 0;
+}
+
+int fakeAddOutput(QString const& filepath)
+{
+ QByteArray tmp = filepath.toUtf8();
+ std::string fileText(tmp.constData(), tmp.size());
+ std::cout << fileText << "#1 - opened for add" << std::endl;
+ return 0;
+}
+
+int main(int argc, char** argv)
+{
+ if (!validateNumberOfArguments(argc, argv)) {
+ std::cout << "Was not able to validate number of arguments: " << argc << std::endl;
+ return -1;
+ }
+
+ if (qstrcmp(argv[1], "revert") == 0) {
+ return fakeRevertOutput();
+ } else if (qstrcmp(argv[1], "sync") == 0) {
+ return fakeSyncOutput();
+ } else if (qstrcmp(argv[1], "submit") == 0) {
+ return fakeSubmitOutput();
+ } else if (qstrcmp(argv[1], "diff2") == 0) {
+ return fakeDiff2Output();
+ } else if (qstrcmp(argv[1], "diff") == 0) {
+ return fakeDiffOutput();
+ } else if (qstrcmp(argv[1], "filelog") == 0) {
+ return fakeFileLogOutput(argv[3]);
+ } else if (qstrcmp(argv[1], "annotate") == 0) {
+ return fakeAnnotateOutput();
+ } else if (qstrcmp(argv[1], "edit") == 0) {
+ return fakeEditOutput(QString(argv[2]));
+ } else if (qstrcmp(argv[1], "fstat") == 0) {
+ return fakeFstatOutput(QString(argv[2]));
+ } else if (qstrcmp(argv[1], "add") == 0) {
+ return fakeAddOutput(QString(argv[2]));
+ }
+ return -1;
+}
diff --git a/plugins/perforce/perforceplugin.h b/plugins/perforce/perforceplugin.h
new file mode 100644
--- /dev/null
+++ b/plugins/perforce/perforceplugin.h
@@ -0,0 +1,183 @@
+/***************************************************************************
+ * Copyright 2010 Morten Danielsen Volden *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ ***************************************************************************/
+
+#ifndef KDEVPERFORCEPLUGIN_H
+#define KDEVPERFORCEPLUGIN_H
+
+#include
+#include
+#include
+#include
+
+
+#include
+#include
+
+#include
+
+class QMenu;
+class QFileInfo;
+class QDir;
+
+
+namespace KDevelop
+{
+class ContextMenuExtension;
+class VcsPluginHelper;
+class DVcsJob;
+}
+
+
+class PerforcePlugin : public KDevelop::IPlugin, public KDevelop::ICentralizedVersionControl
+{
+ Q_OBJECT
+ Q_INTERFACES(KDevelop::IBasicVersionControl KDevelop::ICentralizedVersionControl)
+
+ friend class PerforcePluginTest;
+public:
+ PerforcePlugin(QObject* parent, const QVariantList & = QVariantList());
+ ~PerforcePlugin() override;
+
+ //@{
+ /** Methods inherited from KDevelop::IBasicVersionControl */
+ QString name() const override;
+
+ KDevelop::VcsImportMetadataWidget* createImportMetadataWidget(QWidget* parent) override;
+
+ bool isVersionControlled(const QUrl& localLocation) override;
+
+ KDevelop::VcsJob* repositoryLocation(const QUrl& localLocation) override;
+
+ KDevelop::VcsJob* add(const QList& localLocations,
+ RecursionMode recursion = IBasicVersionControl::Recursive) override;
+ KDevelop::VcsJob* remove(const QList& localLocations) override;
+
+ KDevelop::VcsJob* copy(const QUrl& localLocationSrc,
+ const QUrl& localLocationDstn) override;
+ KDevelop::VcsJob* move(const QUrl& localLocationSrc,
+ const QUrl& localLocationDst) override;
+ KDevelop::VcsJob* status(const QList& localLocations,
+ RecursionMode recursion = IBasicVersionControl::Recursive) override;
+
+ KDevelop::VcsJob* revert(const QList& localLocations,
+ RecursionMode recursion = IBasicVersionControl::Recursive) override;
+
+ KDevelop::VcsJob* update(const QList& localLocations,
+ const KDevelop::VcsRevision& rev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Head),
+ KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override;
+
+ KDevelop::VcsJob* commit(const QString& message,
+ const QList& localLocations,
+ KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override;
+
+ KDevelop::VcsJob* diff(const QUrl& fileOrDirectory,
+ const KDevelop::VcsRevision& srcRevision,
+ const KDevelop::VcsRevision& dstRevision,
+ KDevelop::VcsDiff::Type = KDevelop::VcsDiff::DiffUnified,
+ KDevelop::IBasicVersionControl::RecursionMode recursion = KDevelop::IBasicVersionControl::Recursive) override;
+
+ KDevelop::VcsJob* log(const QUrl& localLocation,
+ const KDevelop::VcsRevision& rev,
+ unsigned long limit = 0) override;
+
+ KDevelop::VcsJob* log(const QUrl& localLocation,
+ const KDevelop::VcsRevision& rev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Base),
+ const KDevelop::VcsRevision& limit = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Start)) override;
+
+ KDevelop::VcsJob* annotate(const QUrl& localLocation,
+ const KDevelop::VcsRevision& rev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Head)) override;
+
+ KDevelop::VcsJob* resolve(const QList& localLocations,
+ KDevelop::IBasicVersionControl::RecursionMode recursion) override;
+
+ KDevelop::VcsJob* createWorkingCopy(const KDevelop::VcsLocation & sourceRepository,
+ const QUrl & destinationDirectory,
+ KDevelop::IBasicVersionControl::RecursionMode recursion = IBasicVersionControl::Recursive) override;
+
+
+ KDevelop::VcsLocationWidget* vcsLocation(QWidget* parent) const override;
+ //@}
+
+ //@{
+ /** Methods inherited from KDevelop::ICentralizedVersionControl */
+ KDevelop::VcsJob* edit(const QUrl& localLocation) override;
+
+ KDevelop::VcsJob* unedit(const QUrl& localLocation) override;
+
+ KDevelop::VcsJob* localRevision(const QUrl& localLocation,
+ KDevelop::VcsRevision::RevisionType) override;
+
+ KDevelop::VcsJob* import(const QString & commitMessage,
+ const QUrl & sourceDirectory,
+ const KDevelop::VcsLocation & destinationRepository) override;
+ //@}
+
+ /// This plugin implements its own edit method
+ KDevelop::VcsJob* edit(const QList& localLocations);
+
+
+ bool hasError() const override;
+ QString errorDescription() const override;
+
+ KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context) override;
+
+
+public Q_SLOTS:
+
+ /// invoked by context-menu
+ void ctxEdit();
+// void ctxUnedit();
+// void ctxLocalRevision();
+// void ctxImport();
+
+private slots:
+ void parseP4StatusOutput(KDevelop::DVcsJob* job);
+ void parseP4DiffOutput(KDevelop::DVcsJob* job);
+ void parseP4LogOutput(KDevelop::DVcsJob* job);
+ void parseP4AnnotateOutput(KDevelop::DVcsJob* job);
+
+
+
+private:
+ bool isValidDirectory(const QUrl & dirPath);
+ KDevelop::DVcsJob* p4fstatJob(const QFileInfo& curFile,
+ KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose);
+
+ bool parseP4fstat(const QFileInfo& curFile,
+ KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose);
+
+ KDevelop::VcsJob* errorsFound(const QString& error,
+ KDevelop::OutputJob::OutputJobVerbosity verbosity = KDevelop::OutputJob::Verbose);
+
+ QString getRepositoryName(const QFileInfo& curFile);
+
+
+ void setEnvironmentForJob(KDevelop::DVcsJob* job, QFileInfo const& fsObject);
+ QList getQvariantFromLogOutput(QStringList const& outputLines);
+
+ std::unique_ptr m_common;
+ QMenu* m_perforcemenu;
+ QString m_perforceConfigName;
+ QString m_perforceExecutable;
+ QAction* m_edit_action;
+
+ bool m_hasError;
+ QString m_errorDescription;
+
+};
+
+#endif // PERFORCEPLUGIN_H
diff --git a/plugins/perforce/perforceplugin.cpp b/plugins/perforce/perforceplugin.cpp
new file mode 100644
--- /dev/null
+++ b/plugins/perforce/perforceplugin.cpp
@@ -0,0 +1,709 @@
+/***************************************************************************
+ * Copyright 2010 Morten Danielsen Volden *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ ***************************************************************************/
+
+#include "perforceplugin.h"
+#include "debug.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+
+using namespace KDevelop;
+
+namespace
+{
+QString toRevisionName(const KDevelop::VcsRevision& rev, QString currentRevision=QString())
+{
+ bool *ok(new bool());
+ int previous = currentRevision.toInt(ok);
+ previous--;
+ QString tmp;
+ switch(rev.revisionType()) {
+ case VcsRevision::Special:
+ switch(rev.revisionValue().value()) {
+ case VcsRevision::Head:
+ return QStringLiteral("#head");
+ case VcsRevision::Base:
+ return QStringLiteral("#have");
+ case VcsRevision::Working:
+ return QStringLiteral("#have");
+ case VcsRevision::Previous:
+ Q_ASSERT(!currentRevision.isEmpty());
+ tmp.setNum(previous);
+ tmp.prepend("#");
+ return tmp;
+ case VcsRevision::Start:
+ return QString();
+ case VcsRevision::UserSpecialType: //Not used
+ Q_ASSERT(false && "i don't know how to do that");
+ }
+ break;
+ case VcsRevision::GlobalNumber:
+ tmp.append("#");
+ tmp.append(rev.revisionValue().toString());
+ return tmp;
+ case VcsRevision::Date:
+ case VcsRevision::FileNumber:
+ case VcsRevision::Invalid:
+ case VcsRevision::UserSpecialType:
+ Q_ASSERT(false);
+ }
+ return QString();
+}
+
+
+VcsItemEvent::Actions actionsFromString(QString const& changeDescription)
+{
+ if(changeDescription == "add")
+ return VcsItemEvent::Added;
+ if(changeDescription == "delete")
+ return VcsItemEvent::Deleted;
+ return VcsItemEvent::Modified;
+}
+
+QDir urlDir(const QUrl& url)
+{
+ QFileInfo f(url.toLocalFile());
+ if(f.isDir())
+ return QDir(url.toLocalFile());
+ else
+ return f.absoluteDir();
+}
+
+}
+
+Q_LOGGING_CATEGORY(PLUGIN_PERFORCE, "kdevplatform.plugins.perforce")
+
+PerforcePlugin::PerforcePlugin(QObject* parent, const QVariantList&):
+ KDevelop::IPlugin("kdevperforce", parent)
+ , m_common(new KDevelop::VcsPluginHelper(this, this))
+ , m_perforcemenu(nullptr)
+ , m_perforceConfigName("p4config.txt")
+ , m_perforceExecutable("p4")
+ , m_edit_action(nullptr)
+ , m_hasError(true)
+{
+ QProcessEnvironment currentEviron(QProcessEnvironment::systemEnvironment());
+ QString tmp(currentEviron.value("P4CONFIG"));
+ if (tmp.isEmpty()) {
+ // We require the P4CONFIG variable to be set because the perforce command line client will need it
+ m_hasError = true;
+ m_errorDescription = i18n("The variable P4CONFIG is not set.");
+ return;
+ } else {
+ m_perforceConfigName = tmp;
+ }
+ m_hasError = false;
+ qCDebug(PLUGIN_PERFORCE) << "The value of P4CONFIG is : " << tmp;
+
+ KDEV_USE_EXTENSION_INTERFACE(KDevelop::IBasicVersionControl)
+ KDEV_USE_EXTENSION_INTERFACE(KDevelop::ICentralizedVersionControl)
+
+}
+
+PerforcePlugin::~PerforcePlugin()
+{
+}
+
+QString PerforcePlugin::name() const
+{
+ return i18n("Perforce");
+}
+
+KDevelop::VcsImportMetadataWidget* PerforcePlugin::createImportMetadataWidget(QWidget* /*parent*/)
+{
+ return nullptr;
+}
+
+bool PerforcePlugin::isValidDirectory(const QUrl & dirPath)
+{
+ const QFileInfo finfo(dirPath.toLocalFile());
+ QDir dir = finfo.isDir() ? QDir(dirPath.toLocalFile()) : finfo.absoluteDir();
+
+ do {
+ if (dir.exists(m_perforceConfigName)) {
+ return true;
+ }
+ } while (dir.cdUp());
+ return false;
+}
+
+bool PerforcePlugin::isVersionControlled(const QUrl& localLocation)
+{
+ QFileInfo fsObject(localLocation.toLocalFile());
+ if (fsObject.isDir()) {
+ return isValidDirectory(localLocation);
+ }
+ return parseP4fstat(fsObject, KDevelop::OutputJob::Silent);
+}
+
+DVcsJob* PerforcePlugin::p4fstatJob(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity)
+{
+ DVcsJob* job = new DVcsJob(curFile.absolutePath(), this, verbosity);
+ setEnvironmentForJob(job, curFile);
+ *job << m_perforceExecutable << "fstat" << curFile.fileName();
+ return job;
+}
+
+bool PerforcePlugin::parseP4fstat(const QFileInfo& curFile, OutputJob::OutputJobVerbosity verbosity)
+{
+ QScopedPointer job(p4fstatJob(curFile, verbosity));
+ if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) {
+ qCDebug(PLUGIN_PERFORCE) << "Perforce returned: " << job->output();
+ if (!job->output().isEmpty())
+ return true;
+ }
+ return false;
+}
+
+QString PerforcePlugin::getRepositoryName(const QFileInfo& curFile)
+{
+ static const QString DEPOT_FILE_STR("... depotFile ");
+ QString ret;
+ QScopedPointer job(p4fstatJob(curFile, KDevelop::OutputJob::Silent));
+ if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) {
+ if (!job->output().isEmpty()) {
+ QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts);
+ foreach(const QString & line, outputLines) {
+ int idx(line.indexOf(DEPOT_FILE_STR));
+ if (idx != -1) {
+ ret = line.right(line.size() - DEPOT_FILE_STR.size());
+ return ret;
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+KDevelop::VcsJob* PerforcePlugin::repositoryLocation(const QUrl& /*localLocation*/)
+{
+ return nullptr;
+}
+
+KDevelop::VcsJob* PerforcePlugin::add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
+{
+ QFileInfo curFile(localLocations.front().toLocalFile());
+ DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
+ setEnvironmentForJob(job, curFile);
+ *job << m_perforceExecutable << "add" << localLocations;
+
+ return job;
+}
+
+KDevelop::VcsJob* PerforcePlugin::remove(const QList& /*localLocations*/)
+{
+ return nullptr;
+}
+
+KDevelop::VcsJob* PerforcePlugin::copy(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDstn*/)
+{
+ return nullptr;
+}
+
+KDevelop::VcsJob* PerforcePlugin::move(const QUrl& /*localLocationSrc*/, const QUrl& /*localLocationDst*/)
+{
+ return nullptr;
+}
+
+KDevelop::VcsJob* PerforcePlugin::status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
+{
+ if (localLocations.count() != 1) {
+ KMessageBox::error(nullptr, i18n("Please select only one item for this operation"));
+ return nullptr;
+ }
+
+ QFileInfo curFile(localLocations.front().toLocalFile());
+
+ DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
+ setEnvironmentForJob(job, curFile);
+ *job << m_perforceExecutable << "fstat" << curFile.fileName();
+ connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4StatusOutput);
+
+ return job;
+}
+
+KDevelop::VcsJob* PerforcePlugin::revert(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
+{
+ if (localLocations.count() != 1) {
+ KMessageBox::error(nullptr, i18n("Please select only one item for this operation"));
+ return nullptr;
+ }
+
+ QFileInfo curFile(localLocations.front().toLocalFile());
+
+ DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
+ setEnvironmentForJob(job, curFile);
+ *job << m_perforceExecutable << "revert" << curFile.fileName();
+
+ return job;
+
+}
+
+KDevelop::VcsJob* PerforcePlugin::update(const QList& localLocations, const KDevelop::VcsRevision& /*rev*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
+{
+ QFileInfo curFile(localLocations.front().toLocalFile());
+
+ DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
+ setEnvironmentForJob(job, curFile);
+ //*job << m_perforceExecutable << "-p" << "127.0.0.1:1666" << "info"; - Let's keep this for now it's very handy for debugging
+ QString fileOrDirectory;
+ if (curFile.isDir())
+ fileOrDirectory = curFile.absolutePath() + "/...";
+ else
+ fileOrDirectory = curFile.fileName();
+ *job << m_perforceExecutable << "sync" << fileOrDirectory;
+ return job;
+}
+
+KDevelop::VcsJob* PerforcePlugin::commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
+{
+ if (localLocations.empty() || message.isEmpty())
+ return errorsFound(i18n("No files or message specified"));
+
+
+ QFileInfo curFile(localLocations.front().toLocalFile());
+
+ DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
+ setEnvironmentForJob(job, curFile);
+ *job << m_perforceExecutable << "submit" << "-d" << message << localLocations;
+
+ return job;
+}
+
+KDevelop::VcsJob* PerforcePlugin::diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, KDevelop::VcsDiff::Type , KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
+{
+ QFileInfo curFile(fileOrDirectory.toLocalFile());
+ QString depotSrcFileName = getRepositoryName(curFile);
+ QString depotDstFileName = depotSrcFileName;
+ depotSrcFileName.append(toRevisionName(srcRevision, dstRevision.prettyValue())); // dstRevision acutally contains the number that we want to take previous of
+
+ DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
+ setEnvironmentForJob(job, curFile);
+ switch (dstRevision.revisionType()) {
+ case VcsRevision::FileNumber:
+ case VcsRevision::GlobalNumber:
+ depotDstFileName.append("#");
+ depotDstFileName.append(dstRevision.prettyValue());
+ *job << m_perforceExecutable << "diff2" << "-u" << depotSrcFileName << depotDstFileName;
+ break;
+ case VcsRevision::Special:
+ switch (dstRevision.revisionValue().value()) {
+ case VcsRevision::Working:
+ *job << m_perforceExecutable << "diff" << "-du" << depotSrcFileName;
+ break;
+ case VcsRevision::Start:
+ case VcsRevision::UserSpecialType:
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+
+ connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4DiffOutput);
+ return job;
+}
+
+KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, long unsigned int limit)
+{
+ static QString lastSeenChangeList;
+ QFileInfo curFile(localLocation.toLocalFile());
+ QString localLocationAndRevStr = localLocation.toLocalFile();
+
+ DVcsJob* job = new DVcsJob(urlDir(localLocation), this, KDevelop::OutputJob::Verbose);
+ setEnvironmentForJob(job, curFile);
+ *job << m_perforceExecutable << "filelog" << "-lit";
+ if(limit > 0)
+ *job << QStringLiteral("-m %1").arg(limit);
+
+ if (curFile.isDir()) {
+ localLocationAndRevStr.append("/...");
+ }
+ QString revStr = toRevisionName(rev, QString());
+ if(!revStr.isEmpty()) {
+ // This is not too nice, but perforce argument for restricting output from filelog does not Work :-(
+ // So putting this in so we do not end up in infinite loop calling log,
+ if(revStr == lastSeenChangeList) {
+ localLocationAndRevStr.append("#none");
+ lastSeenChangeList.clear();
+ } else {
+ localLocationAndRevStr.append(revStr);
+ lastSeenChangeList = revStr;
+ }
+ }
+ *job << localLocationAndRevStr;
+ qCDebug(PLUGIN_PERFORCE) << "Issuing the following command to p4: " << job->dvcsCommand();
+ connect(job, &DVcsJob::readyForParsing, this, &PerforcePlugin::parseP4LogOutput);
+ return job;
+}
+
+KDevelop::VcsJob* PerforcePlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/, const KDevelop::VcsRevision& /*limit*/)
+{
+ QFileInfo curFile(localLocation.toLocalFile());
+ if (curFile.isDir()) {
+ KMessageBox::error(nullptr, i18n("Please select a file for this operation"));
+ return errorsFound(i18n("Directory not supported for this operation"));
+ }
+
+ DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
+ setEnvironmentForJob(job, curFile);
+ *job << m_perforceExecutable << "filelog" << "-lit" << localLocation;
+
+ connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4LogOutput);
+ return job;
+}
+
+KDevelop::VcsJob* PerforcePlugin::annotate(const QUrl& localLocation, const KDevelop::VcsRevision& /*rev*/)
+{
+ QFileInfo curFile(localLocation.toLocalFile());
+ if (curFile.isDir()) {
+ KMessageBox::error(nullptr, i18n("Please select a file for this operation"));
+ return errorsFound(i18n("Directory not supported for this operation"));
+ }
+
+ DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
+ setEnvironmentForJob(job, curFile);
+ *job << m_perforceExecutable << "annotate" << "-qi" << localLocation;
+
+ connect(job, &DVcsJob::readyForParsing, this , &PerforcePlugin::parseP4AnnotateOutput);
+ return job;
+}
+
+KDevelop::VcsJob* PerforcePlugin::resolve(const QList& /*localLocations*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
+{
+ return nullptr;
+}
+
+KDevelop::VcsJob* PerforcePlugin::createWorkingCopy(const KDevelop::VcsLocation& /*sourceRepository*/, const QUrl& /*destinationDirectory*/, KDevelop::IBasicVersionControl::RecursionMode /*recursion*/)
+{
+ return nullptr;
+}
+
+KDevelop::VcsLocationWidget* PerforcePlugin::vcsLocation(QWidget* parent) const
+{
+ return new StandardVcsLocationWidget(parent);
+}
+
+
+KDevelop::VcsJob* PerforcePlugin::edit(const QList& localLocations)
+{
+ QFileInfo curFile(localLocations.front().toLocalFile());
+
+ DVcsJob* job = new DVcsJob(curFile.dir(), this, KDevelop::OutputJob::Verbose);
+ setEnvironmentForJob(job, curFile);
+ *job << m_perforceExecutable << "edit" << localLocations;
+
+ return job;
+}
+
+KDevelop::VcsJob* PerforcePlugin::edit(const QUrl& /*localLocation*/)
+{
+ return nullptr;
+}
+
+KDevelop::VcsJob* PerforcePlugin::unedit(const QUrl& /*localLocation*/)
+{
+ return nullptr;
+}
+
+KDevelop::VcsJob* PerforcePlugin::localRevision(const QUrl& /*localLocation*/, KDevelop::VcsRevision::RevisionType)
+{
+ return nullptr;
+}
+
+KDevelop::VcsJob* PerforcePlugin::import(const QString& /*commitMessage*/, const QUrl& /*sourceDirectory*/, const KDevelop::VcsLocation& /*destinationRepository*/)
+{
+ return nullptr;
+}
+
+KDevelop::ContextMenuExtension PerforcePlugin::contextMenuExtension(KDevelop::Context* context)
+{
+ m_common->setupFromContext(context);
+
+ const QList & ctxUrlList = m_common->contextUrlList();
+
+ bool hasVersionControlledEntries = false;
+ for( const QUrl& url : ctxUrlList) {
+ if (isValidDirectory(url)) {
+ hasVersionControlledEntries = true;
+ break;
+ }
+ }
+
+ if (!hasVersionControlledEntries)
+ return IPlugin::contextMenuExtension(context);
+
+ QMenu * perforceMenu = m_common->commonActions();
+ perforceMenu->addSeparator();
+
+ perforceMenu->addSeparator();
+ if (!m_edit_action) {
+ m_edit_action = new QAction(i18n("Edit"), this);
+ connect(m_edit_action, &QAction::triggered, this, & PerforcePlugin::ctxEdit);
+ }
+ perforceMenu->addAction(m_edit_action);
+
+ ContextMenuExtension menuExt;
+ menuExt.addAction(ContextMenuExtension::VcsGroup, perforceMenu->menuAction());
+
+ return menuExt;
+}
+
+void PerforcePlugin::ctxEdit()
+{
+ QList const & ctxUrlList = m_common->contextUrlList();
+ KDevelop::ICore::self()->runController()->registerJob(edit(ctxUrlList));
+}
+
+void PerforcePlugin::setEnvironmentForJob(DVcsJob* job, const QFileInfo& curFile)
+{
+ KProcess* jobproc = job->process();
+ jobproc->setEnv("P4CONFIG", m_perforceConfigName);
+ if (curFile.isDir()) {
+ jobproc->setEnv("PWD", curFile.filePath());
+ } else {
+ jobproc->setEnv("PWD", curFile.absolutePath());
+ }
+}
+
+QList PerforcePlugin::getQvariantFromLogOutput(QStringList const& outputLines)
+{
+ static const QString LOGENTRY_START("... #");
+ static const QString DEPOTMESSAGE_START("... .");
+ QMap changes;
+ QList commits;
+ QString currentFileName;
+ QString changeNumberStr, author,changeDescription, commitMessage;
+ VcsEvent currentVcsEvent;
+ VcsItemEvent currentRepoFile;
+ VcsRevision rev;
+ int indexofAt;
+ int changeNumber = 0;
+
+ foreach(const QString & line, outputLines) {
+ if (!line.startsWith(LOGENTRY_START) && !line.startsWith(DEPOTMESSAGE_START) && !line.startsWith('\t')) {
+ currentFileName = line;
+ }
+ if(line.indexOf(LOGENTRY_START) != -1)
+ {
+ // expecting the Logentry line to be of the form:
+ //... #5 change 10 edit on 2010/12/06 12:07:31 by mvo@testbed (text)
+ changeNumberStr = line.section(' ', 3, 3 ); // We use global change number
+ changeNumber = changeNumberStr.toInt();
+ author = line.section(' ', 9, 9);
+ changeDescription = line.section(' ' , 4, 4 );
+ indexofAt = author.indexOf('@');
+ author.remove(indexofAt, author.size()); // Only keep the username itself
+ rev.setRevisionValue(changeNumberStr, KDevelop::VcsRevision::GlobalNumber);
+
+ changes[changeNumber].setRevision(rev);
+ changes[changeNumber].setAuthor(author);
+ changes[changeNumber].setDate(QDateTime::fromString(line.section(' ', 6, 7), "yyyy/MM/dd hh:mm:ss"));
+ currentRepoFile.setRepositoryLocation(currentFileName);
+ currentRepoFile.setActions( actionsFromString(changeDescription) );
+ changes[changeNumber].addItem(currentRepoFile);
+ commitMessage.clear(); // We have a new entry, clear message
+ }
+ if (line.startsWith('\t') || line.startsWith(DEPOTMESSAGE_START)) {
+ commitMessage += line.trimmed() + '\n';
+ changes[changeNumber].setMessage(commitMessage);
+ }
+
+ }
+
+ for(auto item : changes) {
+ commits.prepend(QVariant::fromValue(item));
+ }
+ return commits;
+}
+
+
+void PerforcePlugin::parseP4StatusOutput(DVcsJob* job)
+{
+ QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts);
+ QVariantList statuses;
+ QList processedFiles;
+ static const QString ACTION_STR("... action ");
+ static const QString CLIENT_FILE_STR("... clientFile ");
+
+
+
+ VcsStatusInfo status;
+ status.setState(VcsStatusInfo::ItemUserState);
+ foreach(const QString & line, outputLines) {
+ int idx(line.indexOf(ACTION_STR));
+ if (idx != -1) {
+ QString curr = line.right(line.size() - ACTION_STR.size());
+
+ if (curr == "edit") {
+ status.setState(VcsStatusInfo::ItemModified);
+ } else if (curr == "add") {
+ status.setState(VcsStatusInfo::ItemAdded);
+ } else {
+ status.setState(VcsStatusInfo::ItemUserState);
+ }
+ continue;
+ }
+ idx = line.indexOf(CLIENT_FILE_STR);
+ if (idx != -1) {
+ QUrl fileUrl = QUrl::fromLocalFile(line.right(line.size() - CLIENT_FILE_STR.size()));
+
+ status.setUrl(fileUrl);
+ }
+ }
+ statuses.append(qVariantFromValue(status));
+ job->setResults(statuses);
+}
+
+void PerforcePlugin::parseP4LogOutput(KDevelop::DVcsJob* job)
+{
+ QList commits(getQvariantFromLogOutput(job->output().split('\n', QString::SkipEmptyParts)));
+
+ job->setResults(commits);
+}
+
+void PerforcePlugin::parseP4DiffOutput(DVcsJob* job)
+{
+ VcsDiff diff;
+ diff.setDiff(job->output());
+ QDir dir(job->directory());
+
+ do {
+ if (dir.exists(m_perforceConfigName)) {
+ break;
+ }
+ } while (dir.cdUp());
+
+ diff.setBaseDiff(QUrl::fromLocalFile(dir.absolutePath()));
+
+ job->setResults(qVariantFromValue(diff));
+}
+
+void PerforcePlugin::parseP4AnnotateOutput(DVcsJob *job)
+{
+ QVariantList results;
+ /// First get the changelists for this file
+ QStringList strList(job->dvcsCommand());
+ QString localLocation(strList.last()); /// ASSUMPTION WARNING - localLocation is the last in the annotate command
+ KDevelop::VcsRevision dummyRev;
+ QScopedPointer logJob(new DVcsJob(job->directory(), this, OutputJob::Silent));
+ QFileInfo curFile(localLocation);
+ setEnvironmentForJob(logJob.data(), curFile);
+ *logJob << m_perforceExecutable << "filelog" << "-lit" << localLocation;
+
+ QList commits;
+ if (logJob->exec() && logJob->status() == KDevelop::VcsJob::JobSucceeded) {
+ if (!job->output().isEmpty()) {
+ commits = getQvariantFromLogOutput(logJob->output().split('\n', QString::SkipEmptyParts));
+ }
+ }
+
+ VcsEvent item;
+ QMap globalCommits;
+ /// Move the VcsEvents to a more suitable data strucure
+ for (QList::const_iterator commitsIt = commits.constBegin(), commitsEnd = commits.constEnd();
+ commitsIt != commitsEnd; ++commitsIt) {
+ if(commitsIt->canConvert())
+ {
+ item = commitsIt->value();
+ }
+ globalCommits.insert(item.revision().revisionValue().toLongLong(), item);
+ }
+
+ VcsAnnotationLine* annotation;
+ QStringList lines = job->output().split('\n');
+
+ size_t lineNumber(0);
+ QMap definedRevisions;
+ QMap::iterator currentEvent;
+ bool convertToIntOk(false);
+ int globalRevisionInt(0);
+ QString globalRevision;
+ for (QStringList::const_iterator it = lines.constBegin(), itEnd = lines.constEnd();
+ it != itEnd; ++it) {
+ if (it->isEmpty()) {
+ continue;
+ }
+
+ globalRevision = it->left(it->indexOf(':'));
+
+ annotation = new VcsAnnotationLine;
+ annotation->setLineNumber(lineNumber);
+ VcsRevision rev;
+ rev.setRevisionValue(globalRevision, KDevelop::VcsRevision::GlobalNumber);
+ annotation->setRevision(rev);
+ // Find the other info in the commits list
+ globalRevisionInt = globalRevision.toLongLong(&convertToIntOk);
+ if(convertToIntOk)
+ {
+ currentEvent = globalCommits.find(globalRevisionInt);
+ annotation->setAuthor(currentEvent->author());
+ annotation->setCommitMessage(currentEvent->message());
+ annotation->setDate(currentEvent->date());
+ }
+
+ results += qVariantFromValue(*annotation);
+ ++lineNumber;
+ }
+
+ job->setResults(results);
+}
+
+
+KDevelop::VcsJob* PerforcePlugin::errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity)
+{
+ DVcsJob* j = new DVcsJob(QDir::temp(), this, verbosity);
+ *j << "echo" << i18n("error: %1", error) << "-n";
+ return j;
+}
+
+bool PerforcePlugin::hasError() const
+{
+ return m_hasError;
+}
+
+QString PerforcePlugin::errorDescription() const
+{
+ return m_errorDescription;
+}
+
+
diff --git a/plugins/perforce/perforcepluginmetadata.cpp b/plugins/perforce/perforcepluginmetadata.cpp
new file mode 100644
--- /dev/null
+++ b/plugins/perforce/perforcepluginmetadata.cpp
@@ -0,0 +1,34 @@
+/***************************************************************************
+ * Copyright 2015 Morten Danielsen Volden *
+ * *
+ * This program is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU General Public License as *
+ * published by the Free Software Foundation; either version 2 of *
+ * the License or (at your option) version 3 or any later version *
+ * accepted by the membership of KDE e.V. (or its successor approved *
+ * by the membership of KDE e.V.), which shall act as a proxy *
+ * defined in Section 14 of version 3 of the license. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ ***************************************************************************/
+
+#include "perforceplugin.h"
+
+#include
+
+
+// This file only exists so that the tests can be built:
+// test_kdevperforce builds perforceplugin.cpp again but in a different directory.
+// This means that the kdevperforce.json file is no longer found.
+// Since the JSON metadata is not needed in the test, we simply move
+// the K_PLUGIN_FACTORY_WITH_JSON to a separate file.
+
+K_PLUGIN_FACTORY_WITH_JSON(KdevPerforceFactory, "kdevperforce.json", registerPlugin();)
+
+#include "perforcepluginmetadata.moc"
diff --git a/plugins/perforce/test/CMakeLists.txt b/plugins/perforce/test/CMakeLists.txt
new file mode 100644
--- /dev/null
+++ b/plugins/perforce/test/CMakeLists.txt
@@ -0,0 +1,12 @@
+set(perforceplugintest_SRCS
+ test_perforce.cpp
+ ../perforceplugin.cpp
+)
+include_directories(../)
+
+add_definitions( "-DP4_CLIENT_STUB_EXE=\"${CMAKE_CURRENT_BINARY_DIR}/../p4clientstub/p4clientstub\"" )
+
+ecm_add_test(${perforceplugintest_SRCS}
+ TEST_NAME test_kdevperforce
+ LINK_LIBRARIES Qt5::Test KDev::Vcs KDev::Util KDev::Tests )
+
diff --git a/plugins/perforce/test/test_perforce.h b/plugins/perforce/test/test_perforce.h
new file mode 100644
--- /dev/null
+++ b/plugins/perforce/test/test_perforce.h
@@ -0,0 +1,58 @@
+/***************************************************************************
+ * This file was inspired by KDevelop's git plugin *
+ * Copyright 2008 Evgeniy Ivanov *
+ * *
+ * Adapted for Perforce *
+ * Copyright 2011 Morten Danielsen Volden *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ ***************************************************************************/
+
+#ifndef PERFORCEPLUGIN_TEST_H
+#define PERFORCEPLUGIN_TEST_H
+
+#include
+
+class PerforcePlugin;
+
+namespace KDevelop
+{
+class TestCore;
+}
+
+class PerforcePluginTest : public QObject
+{
+ Q_OBJECT
+private slots:
+ void init();
+ void cleanup();
+ void testAdd();
+ void testEdit();
+ void testEditMultipleFiles();
+ void testStatus();
+ void testAnnotate();
+ void testHistory();
+ void testRevert();
+ void testUpdateFile();
+ void testUpdateDir();
+ void testCommit();
+ void testDiff();
+private:
+ PerforcePlugin* m_plugin;
+ KDevelop::TestCore* m_core;
+ void removeTempDirsIfAny();
+ void createNewTempDirs();
+};
+
+#endif
diff --git a/plugins/perforce/test/test_perforce.cpp b/plugins/perforce/test/test_perforce.cpp
new file mode 100644
--- /dev/null
+++ b/plugins/perforce/test/test_perforce.cpp
@@ -0,0 +1,190 @@
+/***************************************************************************
+ * This file was inspired by KDevelop's git plugin *
+ * Copyright 2008 Evgeniy Ivanov *
+ * *
+ * Adapted for Perforce *
+ * Copyright 2011 Morten Danielsen Volden *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ ***************************************************************************/
+
+#include "test_perforce.h"
+
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+
+#define VERIFYJOB(j) \
+ QVERIFY(j); QVERIFY(j->exec()); QVERIFY((j)->status() == VcsJob::JobSucceeded)
+
+const QString tempDir = QDir::tempPath();
+const QString perforceTestBaseDirNoSlash(tempDir + "/kdevPerforce_testdir");
+const QString perforceTestBaseDir(tempDir + "/kdevPerforce_testdir/");
+const QString perforceTestBaseDir2(tempDir + "/kdevPerforce_testdir2/");
+const QString perforceConfigFileName("p4config.txt");
+
+const QString perforceSrcDir(perforceTestBaseDir + "src/");
+const QString perforceTest_FileName("testfile");
+const QString perforceTest_FileName2("foo");
+const QString perforceTest_FileName3("bar");
+
+using namespace KDevelop;
+
+void PerforcePluginTest::init()
+{
+ AutoTestShell::init({"kdevperforce"});
+ m_core = new TestCore();
+ m_core->initialize();
+ m_plugin = new PerforcePlugin(m_core);
+ /// During test we are setting the executable the plugin uses to our own stub
+ m_plugin->m_perforceExecutable = P4_CLIENT_STUB_EXE;
+ removeTempDirsIfAny();
+ createNewTempDirs();
+}
+
+void PerforcePluginTest::createNewTempDirs()
+{
+ // Now create the basic directory structure
+ QDir tmpdir(tempDir);
+ tmpdir.mkdir(perforceTestBaseDir);
+ //we start it after repoInit, so we still have empty repo
+ QFile f(perforceTestBaseDir + perforceConfigFileName);
+ if (f.open(QIODevice::WriteOnly)) {
+ QTextStream input(&f);
+ input << "P4PORT=127.0.0.1:1666\n";
+ input << "P4USER=mvo\n";
+ input << "P4CLIENT=testbed\n";
+ }
+ f.close();
+
+ //Put a file here because the annotate and update function will check for that
+ QFile g(perforceTestBaseDir + perforceTest_FileName);
+ if (g.open(QIODevice::WriteOnly)) {
+ QTextStream input(&g);
+ input << "HELLO WORLD";
+ }
+ g.close();
+
+
+ tmpdir.mkdir(perforceSrcDir);
+ tmpdir.mkdir(perforceTestBaseDir2);
+}
+
+
+void PerforcePluginTest::removeTempDirsIfAny()
+{
+ QDir dir(perforceTestBaseDir);
+ if (dir.exists() && !dir.removeRecursively())
+ qDebug() << "QDir::removeRecursively(" << perforceTestBaseDir << ") returned false";
+
+ QDir dir2(perforceTestBaseDir);
+ if (dir2.exists() && !dir2.removeRecursively())
+ qDebug() << "QDir::removeRecursively(" << perforceTestBaseDir2 << ") returned false";
+}
+
+
+void PerforcePluginTest::cleanup()
+{
+ m_core->cleanup();
+ delete m_core;
+ removeTempDirsIfAny();
+}
+
+void PerforcePluginTest::testAdd()
+{
+ VcsJob* j = m_plugin->add(QList({ QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName) } ));
+ VERIFYJOB(j);
+}
+
+void PerforcePluginTest::testEdit()
+{
+ VcsJob* j = m_plugin->edit(QList( { QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName) } ));
+ VERIFYJOB(j);
+}
+
+void PerforcePluginTest::testEditMultipleFiles()
+{
+ QList filesForEdit;
+ filesForEdit.push_back(QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName));
+ filesForEdit.push_back(QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName2));
+ filesForEdit.push_back(QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName3));
+ VcsJob* j = m_plugin->edit(filesForEdit);
+ VERIFYJOB(j);
+}
+
+
+void PerforcePluginTest::testStatus()
+{
+ VcsJob* j = m_plugin->status(QList( { QUrl::fromLocalFile(perforceTestBaseDirNoSlash) } ));
+ VERIFYJOB(j);
+}
+
+void PerforcePluginTest::testAnnotate()
+{
+ VcsJob* j = m_plugin->annotate(QUrl( QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName) ));
+ VERIFYJOB(j);
+}
+
+void PerforcePluginTest::testHistory()
+{
+ VcsJob* j = m_plugin->log(QUrl( QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName) ));
+ VERIFYJOB(j);
+}
+
+void PerforcePluginTest::testRevert()
+{
+ VcsJob* j = m_plugin->revert(QList( { QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName) } ));
+ VERIFYJOB(j);
+}
+
+void PerforcePluginTest::testUpdateFile()
+{
+ VcsJob* j = m_plugin->update(QList( { QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName) } ));
+ VERIFYJOB(j);
+}
+
+void PerforcePluginTest::testUpdateDir()
+{
+ VcsJob* j = m_plugin->update(QList( { QUrl::fromLocalFile(perforceTestBaseDirNoSlash) } ));
+ VERIFYJOB(j);
+}
+
+void PerforcePluginTest::testCommit()
+{
+ QString commitMsg("this is the commit message");
+ VcsJob* j = m_plugin->commit(commitMsg, QList( { QUrl::fromLocalFile(perforceTestBaseDirNoSlash) } ));
+ VERIFYJOB(j);
+}
+
+void PerforcePluginTest::testDiff()
+{
+ VcsRevision srcRevision;
+ srcRevision.setRevisionValue(QVariant(1), VcsRevision::GlobalNumber);
+ VcsRevision dstRevision;
+ dstRevision.setRevisionValue(QVariant(2), VcsRevision::GlobalNumber);
+
+ VcsJob* j = m_plugin->diff( QUrl::fromLocalFile(perforceTestBaseDir + perforceTest_FileName), srcRevision, dstRevision, VcsDiff::Type::DiffUnified );
+ VERIFYJOB(j);
+}
+
+
+QTEST_MAIN(PerforcePluginTest)
+