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) +