diff --git a/plugins/perforce/CMakeLists.txt b/plugins/perforce/CMakeLists.txt index 3087a9b9dc..10bd980756 100644 --- a/plugins/perforce/CMakeLists.txt +++ b/plugins/perforce/CMakeLists.txt @@ -1,22 +1,26 @@ add_subdirectory(p4clientstub) ecm_qt_declare_logging_category(kdevperforce_LOG_PART_SRCS HEADER debug.h IDENTIFIER PLUGIN_PERFORCE CATEGORY_NAME "kdevelop.plugins.perforce" ) +set(kdevperforce_UIS + ui/perforceimportmetadatawidget.ui +) set(kdevperforce_PART_SRCS + ui/perforceimportmetadatawidget.cpp perforceplugin.cpp perforcepluginmetadata.cpp ${kdevperforce_LOG_PART_SRCS} ) - +ki18n_wrap_ui(kdevperforce_PART_SRCS ${kdevperforce_UIS}) kdevplatform_add_plugin(kdevperforce JSON kdevperforce.json SOURCES ${kdevperforce_PART_SRCS}) target_link_libraries(kdevperforce KDev::Interfaces KDev::Vcs ) if(BUILD_TESTING) add_subdirectory(tests) endif() diff --git a/plugins/perforce/perforceplugin.cpp b/plugins/perforce/perforceplugin.cpp index 0455260856..2087255a93 100644 --- a/plugins/perforce/perforceplugin.cpp +++ b/plugins/perforce/perforceplugin.cpp @@ -1,690 +1,691 @@ /*************************************************************************** * 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 "ui/perforceimportmetadatawidget.h" #include "debug.h" #include "qtcompat_p.h" #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, const QString& currentRevision=QString()) { bool ok; 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 == QLatin1String("add")) return VcsItemEvent::Added; if(changeDescription == QLatin1String("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(); } } PerforcePlugin::PerforcePlugin(QObject* parent, const QVariantList&): KDevelop::IPlugin(QStringLiteral("kdevperforce"), parent) , m_common(new KDevelop::VcsPluginHelper(this, this)) , m_perforceConfigName(QStringLiteral("p4config.txt")) , m_perforceExecutable(QStringLiteral("p4")) , m_edit_action(nullptr) { QProcessEnvironment currentEviron(QProcessEnvironment::systemEnvironment()); QString tmp(currentEviron.value(QStringLiteral("P4CONFIG"))); if (tmp.isEmpty()) { // We require the P4CONFIG variable to be set because the perforce command line client will need it setErrorDescription(i18n("The variable P4CONFIG is not set. Is perforce installed on the system?")); return; } else { m_perforceConfigName = tmp; } qCDebug(PLUGIN_PERFORCE) << "The value of P4CONFIG is : " << tmp; } PerforcePlugin::~PerforcePlugin() { } QString PerforcePlugin::name() const { return i18n("Perforce"); } -KDevelop::VcsImportMetadataWidget* PerforcePlugin::createImportMetadataWidget(QWidget* /*parent*/) +KDevelop::VcsImportMetadataWidget* PerforcePlugin::createImportMetadataWidget(QWidget* parent) { - return nullptr; + return new PerforceImportMetadataWidget(parent); } bool PerforcePlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation) { Q_UNUSED(remoteLocation); // TODO return false; } 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(QStringLiteral("... 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::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, QWidget* parent) { 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, parent); QMenu * perforceMenu = m_common->commonActions(parent); 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(QStringLiteral("P4CONFIG"), m_perforceConfigName); if (curFile.isDir()) { jobproc->setEnv(QStringLiteral("PWD"), curFile.filePath()); } else { jobproc->setEnv(QStringLiteral("PWD"), curFile.absolutePath()); } } QList PerforcePlugin::getQvariantFromLogOutput(QStringList const& outputLines) { static const QString LOGENTRY_START(QStringLiteral("... #")); static const QString DEPOTMESSAGE_START(QStringLiteral("... .")); 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), QStringLiteral("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(const auto& item : qAsConst(changes)) { commits.prepend(QVariant::fromValue(item)); } return commits; } void PerforcePlugin::parseP4StatusOutput(DVcsJob* job) { QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); QVariantList statuses; static const QString ACTION_STR(QStringLiteral("... action ")); static const QString CLIENT_FILE_STR(QStringLiteral("... 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 == QLatin1String("edit")) { status.setState(VcsStatusInfo::ItemModified); } else if (curr == QLatin1String("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); } QStringList lines = job->output().split('\n'); size_t lineNumber(0); 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(':')); VcsAnnotationLine annotation; 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; } diff --git a/plugins/perforce/tests/CMakeLists.txt b/plugins/perforce/tests/CMakeLists.txt index 5fb5869c8f..4a96493e1f 100644 --- a/plugins/perforce/tests/CMakeLists.txt +++ b/plugins/perforce/tests/CMakeLists.txt @@ -1,16 +1,17 @@ include_directories( .. ${CMAKE_CURRENT_BINARY_DIR}/.. ) set(perforceplugintest_SRCS test_perforce.cpp ../perforceplugin.cpp + ../ui/perforceimportmetadatawidget.cpp ${kdevperforce_LOG_PART_SRCS} ) 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/ui/perforceimportmetadatawidget.cpp b/plugins/perforce/ui/perforceimportmetadatawidget.cpp new file mode 100644 index 0000000000..39b46eb775 --- /dev/null +++ b/plugins/perforce/ui/perforceimportmetadatawidget.cpp @@ -0,0 +1,210 @@ +/*************************************************************************** +* This file is part of KDevelop Perforce plugin, KDE project * +* * +* Copyright 2018 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 "perforceimportmetadatawidget.h" + +#include + +#include +#include +#include +#include + +using namespace KDevelop; + +PerforceImportMetadataWidget::PerforceImportMetadataWidget(QWidget* parent) + : VcsImportMetadataWidget(parent) + , m_ui(new Ui::PerforceImportMetadataWidget) +{ + m_ui->setupUi(this); + + m_ui->executableLoc->setText("/usr/bin/p4"); + m_ui->p4portEdit->setText("perforce:1666"); + + QProcessEnvironment curEnv = QProcessEnvironment::systemEnvironment(); + m_ui->p4configEdit->setText(curEnv.contains("P4CONFIG") ? curEnv.value("P4CONFIG") : ""); + m_ui->p4portEdit->setText(curEnv.contains("P4PORT") ? curEnv.value("P4PORT") : ""); + m_ui->p4userEdit->setText(curEnv.contains("P4USER") ? curEnv.value("P4USER") : ""); + curEnv.contains("P4CONFIG") ? m_ui->radioButtonConfig->setChecked(true) : m_ui->radioButtonVariables->setChecked(true); + curEnv.contains("P4CONFIG") ? m_ui->p4configEdit->setEnabled(true) : m_ui->p4configEdit->setEnabled(false); + + m_ui->sourceLoc->setEnabled(false); + m_ui->sourceLoc->setMode(KFile::Directory); + + m_ui->errorMsg->setTextColor(QColor(255, 0, 0)); + m_ui->errorMsg->setReadOnly(true); + + m_ui->p4clientEdit->setEditable(true); + + connect(m_ui->p4clientEdit, static_cast(&KComboBox::returnPressed), this, &PerforceImportMetadataWidget::changed ); + + connect(m_ui->radioButtonConfig, &QRadioButton::clicked, m_ui->p4configEdit, &QLineEdit::setEnabled); + connect(m_ui->radioButtonVariables, &QRadioButton::clicked, m_ui->p4configEdit, &QLineEdit::setDisabled); + + connect(m_ui->testP4setupButton, &QPushButton::pressed, this, &PerforceImportMetadataWidget::testP4setup); +} + +QUrl PerforceImportMetadataWidget::source() const +{ + return m_ui->sourceLoc->url(); +} + +VcsLocation PerforceImportMetadataWidget::destination() const +{ + VcsLocation dest; + dest.setRepositoryServer(m_ui->p4portEdit->text()); + dest.setUserData(QVariant::fromValue(m_ui->p4userEdit->text())); + dest.setRepositoryBranch(m_ui->p4clientEdit->itemText(0)); + return dest; +} + +QString PerforceImportMetadataWidget::message() const +{ + return QString(); + //TODO: return m_ui->message->toPlainText(); +} + +void PerforceImportMetadataWidget::setSourceLocation(const VcsLocation& url) +{ + m_ui->sourceLoc->setUrl(url.localUrl()); +} + +void PerforceImportMetadataWidget::setSourceLocationEditable(bool enable) +{ + m_ui->sourceLoc->setEnabled(enable); +} + +void PerforceImportMetadataWidget::setMessage(const QString& message) +{ + Q_UNUSED(message); + + //FIXME: correct ui field needs to be set + //m_ui->message->setText(message); +} + +bool PerforceImportMetadataWidget::hasValidData() const +{ + // FIXME: It has valid data if testP4setup has completed correctly. AND client name has been set to something + return !m_ui->p4clientEdit->itemText(0).isEmpty(); +} + +void PerforceImportMetadataWidget::testP4setup() +{ + m_ui->errorMsg->clear(); + m_ui->p4clientEdit->clear(); + + if (!validateP4executable()) + return; + + QDir execDir(m_ui->sourceLoc->url().toLocalFile()); + QTemporaryDir tmpDir; + if (!execDir.exists()) + execDir = tmpDir.path(); + + if(!validateP4port(execDir.path())) + return; + + if(!validateP4user(execDir.path())) + return; + + emit changed(); +} + +bool PerforceImportMetadataWidget::validateP4executable() +{ + if (QStandardPaths::findExecutable(m_ui->executableLoc->url().toLocalFile()).isEmpty()) { + m_ui->errorMsg->setText("Unable to find perforce executable. Is it installed on the system? Is it in your PATH?"); + return false; + } + return true; +} + +bool PerforceImportMetadataWidget::validateP4user(const QString& projectDir) const +{ + QProcess exec; + QProcessEnvironment p4execEnvironment; + p4execEnvironment.insert(QString("P4PORT"), m_ui->p4portEdit->displayText()); + exec.setWorkingDirectory(projectDir); + exec.setProcessEnvironment(p4execEnvironment); + exec.start(m_ui->executableLoc->url().toLocalFile(), QStringList() << QStringLiteral("workspaces") << + QStringLiteral("-u") << m_ui->p4userEdit->text() + ); + exec.waitForFinished(); + + QString processStdout(exec.readAllStandardOutput()); + QString processStderr(exec.readAllStandardError()); + +// std::cout << "Exited with code: " << exec.exitCode() << std::endl; +// std::cout << "Exited with stdout" << processStdout.toStdString() << std::endl; +// std::cout << "Exited with stderr" << processStderr.toStdString() << std::endl; + if (exec.exitCode() != 0) { + if(!processStderr.isEmpty()) { + m_ui->errorMsg->setText(processStderr); + } else { + QString msg("P4 Client failed with exit code: "); + msg += QString::number(exec.exitCode()); + m_ui->errorMsg->setText(msg); + } + return false; + } + if(!processStdout.isEmpty()) { + QStringList clientCmdOutput = processStdout.split("\n",QString::SkipEmptyParts); + QStringList clientItems; + for(QString const& clientLine : clientCmdOutput) { + QStringList wordsInLine = clientLine.split(" "); + // Client mvo_testkdevinteg 2017/05/22 root C:\P4repo 'Created by mvo. ' -- Line would be expected to look like so + clientItems.append(wordsInLine.at(1)); + } + m_ui->p4clientEdit->addItems(clientItems); + } + return true; +} + +bool PerforceImportMetadataWidget::validateP4port(const QString& projectDir) const +{ + QProcess exec; + QProcessEnvironment p4execEnvironment; + p4execEnvironment.insert(QString("P4PORT"), m_ui->p4portEdit->displayText()); + QTextStream out(stdout); + for (QString x : p4execEnvironment.toStringList()) { + out << x << endl; + } + + exec.setWorkingDirectory(projectDir); + exec.setProcessEnvironment(p4execEnvironment); + exec.start(m_ui->executableLoc->url().toLocalFile(), QStringList() << QStringLiteral("info")); + exec.waitForFinished(); + //QString processStdout(exec.readAllStandardOutput()); + QString processStderr(exec.readAllStandardError()); + + //std::cout << "Exited with code: " << exec.exitCode() << std::endl; + //std::cout << "Exited with stdout" << processStdout.toStdString() << std::endl; + //std::cout << "Exited with stderr" << processStderr.toStdString() << std::endl; + if (exec.exitCode() != 0) { + if(!processStderr.isEmpty()) { + m_ui->errorMsg->setText(processStderr); + } else { + QString msg("P4 Client failed with error code: "); + msg += QString::number(exec.exitCode()); + m_ui->errorMsg->setText(msg); + } + return false; + } + return true; +} diff --git a/plugins/perforce/ui/perforceimportmetadatawidget.h b/plugins/perforce/ui/perforceimportmetadatawidget.h new file mode 100644 index 0000000000..055748d02b --- /dev/null +++ b/plugins/perforce/ui/perforceimportmetadatawidget.h @@ -0,0 +1,63 @@ +/*************************************************************************** +* This file is part of KDevelop Perforce plugin, KDE project * +* * +* Copyright 2018 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 KDEVPLATFORM_PERFORCEIMPORTMETADATAWIDGET_H +#define KDEVPLATFORM_PERFORCEIMPORTMETADATAWIDGET_H + +#include "ui_perforceimportmetadatawidget.h" + +#include + +/** + * Asks the user for all options needed to import an existing directory into + * a Perforce repository + */ +class KDEVPLATFORMVCS_EXPORT PerforceImportMetadataWidget + : public KDevelop::VcsImportMetadataWidget +{ + Q_OBJECT + +public: + PerforceImportMetadataWidget(QWidget* parent = nullptr); + + QUrl source() const override; + KDevelop::VcsLocation destination() const override; + // TODO: Is not used, it returns an empty string + QString message() const override; + void setSourceLocation(const KDevelop::VcsLocation&) override; + void setSourceLocationEditable(bool) override; + void setMessage(const QString& message) override; + bool hasValidData() const override; + +private Q_SLOTS: + void testP4setup(); + +private: + + bool validateP4executable(); + + bool validateP4port(const QString& projectDir) const; + + bool validateP4user(const QString& projectDir) const; + + Ui::PerforceImportMetadataWidget* m_ui; + QString m_errorDescription; +}; + +#endif diff --git a/plugins/perforce/ui/perforceimportmetadatawidget.ui b/plugins/perforce/ui/perforceimportmetadatawidget.ui new file mode 100644 index 0000000000..9cb99b127b --- /dev/null +++ b/plugins/perforce/ui/perforceimportmetadatawidget.ui @@ -0,0 +1,238 @@ + + + PerforceImportMetadataWidget + + + + 0 + 0 + 581 + 339 + + + + Import + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Source directory: + + + + + + + + + + P4 executable: + + + + + + + + + + P4 Port + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + The format of P4PORT for Perforce applications is protocol:host:port, or port by itself if both the Perforce application and versioning service are running on the same host. Port numbers must be in the range 1024 through 32767 + + + + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + P4 User + + + + + + + Current Perforce user name. By default, the Perforce username is the same as the OS username + + + + + + + P4 Client + + + + + + + Name of current client workspace on the Perforce server + + + + + + + P4 Config + + + + + + + false + + + Contains a file name without a path. The specified file is used to store other Perforce environment variables + + + + + + + + 50 + 0 + + + + Test + + + + + + + * { background-color: rgba(0, 0, 0, 0); } + + + + + + + true + + + + 0 + 0 + + + + + 250 + 0 + + + + Qt::LeftToRight + + + false + + + + + + true + + + false + + + false + + + + + 7 + 10 + 100 + 30 + + + + Config File + + + true + + + + + + 115 + 10 + 140 + 31 + + + + User/Port Config + + + + + + + + + KComboBox + QComboBox +
kcombobox.h
+
+ + KUrlRequester + QWidget +
kurlrequester.h
+ 1 +
+
+ + +