diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e8c0e3..c68a84b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,96 +1,97 @@ project(mercurial) cmake_minimum_required(VERSION 2.8.12) find_package (ECM 0.0.9 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${mercurial_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH}) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMAddTests) include(ECMOptionalAddSubdirectory) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMMarkAsTest) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(ECMPackageConfigHelpers) include(GenerateExportHeader) include(FeatureSummary) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) find_package(KF5 REQUIRED COMPONENTS Config IconThemes I18n ItemModels ItemViews KCMUtils KIO) find_package(KDevPlatform 5.0.0 REQUIRED) include_directories( ${mercurial_BINARY_DIR} ${mercurial_SOURCE_DIR} ) add_definitions( -DQT_DEPRECATED_WARNINGS -DQT_DISABLE_DEPRECATED_BEFORE=0x050400 -DQT_NO_URL_CAST_FROM_STRING -DQT_STRICT_ITERATORS -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS ) check_cxx_compiler_flag(-Wno-missing-field-initializers HAVE_MFI_FLAG) check_cxx_compiler_flag(-Werror=undefined-bool-conversion HAVE_UBC_FLAG) check_cxx_compiler_flag(-Werror=tautological-undefined-compare HAVE_TUC_FLAG) if (HAVE_MFI_FLAG) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-field-initializers") endif() if (HAVE_UBC_FLAG) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=undefined-bool-conversion") endif() if (HAVE_TUC_FLAG) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=tautological-undefined-compare") endif() ########### next target ############### set(kdevmercurial_PART_SRCS ui/mercurialqueuesmanager.cpp ui/mercurialheadswidget.cpp models/mercurialqueueseriesmodel.cpp models/mercurialheadsmodel.cpp mercurialpushjob.cpp mercurialannotatejob.cpp mercurialvcslocationwidget.cpp mercurialplugin.cpp mercurialpluginmetadata.cpp + mercurialjob.cpp ) set(kdevmercurial_UIS ui/mercurialheadswidget.ui ui/mercurialqueuesmanager.ui ) ki18n_wrap_ui(kdevmercurial_PART_SRCS ${kdevmercurial_UIS}) kdevplatform_add_plugin(kdevmercurial JSON kdevmercurial.json SOURCES ${kdevmercurial_PART_SRCS}) target_link_libraries(kdevmercurial KDev::Util KDev::Interfaces KDev::Vcs KDev::Project ) # process tests subfolder only after ui files have been registered to cmake above # so that proper dependencies are created for the included generated headers add_subdirectory(tests) add_subdirectory(icons) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/mercurialannotatejob.cpp b/mercurialannotatejob.cpp index a7d8b6e..ab61858 100644 --- a/mercurialannotatejob.cpp +++ b/mercurialannotatejob.cpp @@ -1,297 +1,259 @@ /* This file is part of KDevelop Copyright 2016 Sergey Kalinichev This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mercurialannotatejob.h" #include #include #include #include "debug.h" using namespace KDevelop; MercurialAnnotateJob::MercurialAnnotateJob(const QDir &workingDir, const VcsRevision& revision, const QUrl& location, MercurialPlugin *parent) - : VcsJob(parent, KDevelop::OutputJob::Silent), - m_workingDir(workingDir), + : MercurialJob(workingDir, parent, JobType::Annotate), m_revision(revision), - m_location(location), - m_status(JobNotStarted) -{ - setType(JobType::Annotate); - setCapabilities(Killable); -} - -bool MercurialAnnotateJob::doKill() -{ - m_status = JobCanceled; - if (m_job) { - return m_job->kill(KJob::Quietly); - } - return true; -} + m_location(location) +{} void MercurialAnnotateJob::start() { m_status = JobRunning; DVcsJob *job = new DVcsJob(m_workingDir, vcsPlugin(), KDevelop::OutputJob::Silent); if (Q_UNLIKELY(m_testCase == TestCase::Status)) { *job << "some_command_that_doesnt_exist"; } else { *job << "hg" << "status" << "-m" << "-a" << "-n"; *job << "--" << m_location.toLocalFile(); } connect(job, &DVcsJob::resultsReady, this, &MercurialAnnotateJob::parseStatusResult); connect(job, &KJob::finished, this, &MercurialAnnotateJob::subJobFinished); m_job = job; job->start(); } void MercurialAnnotateJob::parseCommitResult(KDevelop::VcsJob* j) { DVcsJob *job = static_cast(j); if (job->status() != VcsJob::JobSucceeded) return setFail(); launchAnnotateJob(); } void MercurialAnnotateJob::launchAnnotateJob() const { DVcsJob *annotateJob = new DVcsJob(m_workingDir, vcsPlugin(), KDevelop::OutputJob::Silent); if (Q_UNLIKELY(m_testCase == TestCase::Annotate)) { *annotateJob << "some_command_that_doesnt_exist"; } else { *annotateJob << "hg" << "annotate" << "-n" << "-d"; *annotateJob << "--" << m_location.toLocalFile(); } connect(annotateJob, &DVcsJob::resultsReady, this, &MercurialAnnotateJob::parseAnnotateOutput); connect(annotateJob, &KJob::finished, this, &MercurialAnnotateJob::subJobFinished); m_job = annotateJob; annotateJob->start(); } void MercurialAnnotateJob::parseStatusResult(KDevelop::VcsJob* j) { DVcsJob *job = static_cast(j); if (job->status() != VcsJob::JobSucceeded) return setFail(); if (job->output().isEmpty()) { launchAnnotateJob(); } else { m_hasModifiedFile = true; DVcsJob *commitJob = new DVcsJob(m_workingDir, vcsPlugin(), KDevelop::OutputJob::Silent); if (Q_UNLIKELY(m_testCase == TestCase::Commit)) { *commitJob << "some_command_that_doesnt_exist"; } else { *commitJob << "hg" << "commit" << "-u" << "not.committed.yet" << "-m" << "Not Committed Yet"; *commitJob << "--" << m_location.toLocalFile(); } connect(commitJob, &DVcsJob::resultsReady, this, &MercurialAnnotateJob::parseCommitResult); connect(commitJob, &KJob::finished, this, &MercurialAnnotateJob::subJobFinished); m_job = commitJob; commitJob->start(); } } void MercurialAnnotateJob::parseStripResult(KDevelop::VcsJob* /*job*/) { setSuccess(); } QVariant MercurialAnnotateJob::fetchResults() { return m_annotations; } -VcsJob::JobStatus MercurialAnnotateJob::status() const -{ - return m_status; -} - -IPlugin *MercurialAnnotateJob::vcsPlugin() const -{ - return static_cast(parent()); -} - void MercurialAnnotateJob::parseAnnotateOutput(VcsJob *j) { DVcsJob *job = static_cast(j); if (job->status() != VcsJob::JobSucceeded) return setFail(); QStringList lines = job->output().split('\n'); lines.removeLast(); static const QString reAnnotPat("\\s*(\\d+)\\s+(\\w+ \\w+ \\d\\d \\d\\d:\\d\\d:\\d\\d \\d\\d\\d\\d .\\d\\d\\d\\d): ([^\n]*)"); QRegExp reAnnot(reAnnotPat, Qt::CaseSensitive, QRegExp::RegExp2); unsigned int lineNumber = 0; foreach (const QString& line, lines) { if (!reAnnot.exactMatch(line)) { mercurialDebug() << "Could not parse annotation line: \"" << line << '\"'; return setFail(); } VcsAnnotationLine annotation; annotation.setLineNumber(lineNumber++); annotation.setText(reAnnot.cap(3)); bool success = false; qlonglong rev = reAnnot.cap(1).toLongLong(&success); if (!success) { mercurialDebug() << "Could not parse revision in annotation line: \"" << line << '\"'; return setFail(); } QDateTime dt = QDateTime::fromString(reAnnot.cap(2).left(reAnnot.cap(2).lastIndexOf(" ")), "ddd MMM dd hh:mm:ss yyyy"); //mercurialDebug() << reAnnot.cap(2).left(reAnnot.cap(2).lastIndexOf(" ")) << dt; Q_ASSERT(dt.isValid()); annotation.setDate(dt); VcsRevision vcsrev; vcsrev.setRevisionValue(rev, VcsRevision::GlobalNumber); annotation.setRevision(vcsrev); m_annotations.push_back(qVariantFromValue(annotation)); } for (const auto& annotation: m_annotations) { const auto revision = static_cast(vcsPlugin())->toMercurialRevision(annotation.value().revision()); if (!m_revisionsToLog.contains(revision)) { m_revisionsToLog.insert(revision); } } nextPartOfLog(); } void MercurialAnnotateJob::nextPartOfLog() { m_status = JobRunning; DVcsJob *logJob = new DVcsJob(m_workingDir, vcsPlugin(), KDevelop::OutputJob::Silent); if (Q_UNLIKELY(m_testCase == TestCase::Log)) { *logJob << "some_command_that_doesnt_exist"; } else { *logJob << "hg" << "log" << "--template" << "{rev}\\_%{desc|firstline}\\_%{author}\\_%"; } int numRevisions = 0; for (auto it = m_revisionsToLog.begin(); it != m_revisionsToLog.end();) { *logJob << "-r" << *it; it = m_revisionsToLog.erase(it); if (++numRevisions >= 200) { // Prevent mercurial crash break; } } connect(logJob, &DVcsJob::resultsReady, this, &MercurialAnnotateJob::parseLogOutput); connect(logJob, &KJob::finished, this, &MercurialAnnotateJob::subJobFinished); m_job = logJob; logJob->start(); } void MercurialAnnotateJob::parseLogOutput(KDevelop::VcsJob* j) { DVcsJob *job = static_cast(j); auto items = job->output().split("\\_%"); items.removeLast(); if (items.size() % 3) { mercurialDebug() << "Cannot parse annotate log: unexpected number of entries" << items.size() << m_annotations.size(); return setFail(); } for (auto it = items.constBegin(); it != items.constEnd(); it += 3) { if (!m_revisionsCache.contains(*it)) { m_revisionsCache[*it] = {*(it + 1), *(it + 2)}; } } if (!m_revisionsToLog.isEmpty()) { // TODO: We can show what we've got so far return nextPartOfLog(); } for (int idx = 0; idx < m_annotations.size(); idx++) { auto annotationLine = m_annotations[idx].value(); const auto revision = static_cast(vcsPlugin())->toMercurialRevision(annotationLine.revision()); if (!m_revisionsCache.contains(revision)) { Q_ASSERT(m_revisionsCache.contains(revision)); continue; } mercurialDebug() << m_revisionsCache.value(revision).first << m_revisionsCache.value(revision).second; if (m_revisionsCache.value(revision).first.isEmpty() && m_revisionsCache.value(revision).second.isEmpty()) { mercurialDebug() << "Strange revision" << revision; continue; } annotationLine.setCommitMessage(m_revisionsCache.value(revision).first); annotationLine.setAuthor(m_revisionsCache.value(revision).second); m_annotations[idx] = qVariantFromValue(annotationLine); } if (m_hasModifiedFile) { DVcsJob* stripJob = new DVcsJob(m_workingDir, vcsPlugin(), KDevelop::OutputJob::Silent); if (Q_UNLIKELY(m_testCase == TestCase::Strip)) { *stripJob << "some_command_that_doesnt_exist"; } else { *stripJob << "hg" << "--config" << "extensions.mq=" << "strip" << "-k" << "tip"; } connect(stripJob, &DVcsJob::resultsReady, this, &MercurialAnnotateJob::parseStripResult); connect(stripJob, &KJob::finished, this, &MercurialAnnotateJob::subJobFinished); m_job = stripJob; stripJob->start(); } else { setSuccess(); } } -void MercurialAnnotateJob::setFail() -{ - m_status = JobFailed; - emitResult(); - emit resultsReady(this); -} - -void MercurialAnnotateJob::setSuccess() -{ - m_status = JobSucceeded; - emitResult(); - emit resultsReady(this); -} - void MercurialAnnotateJob::subJobFinished(KJob* job) { if (job->error()) { setFail(); } } diff --git a/mercurialannotatejob.h b/mercurialannotatejob.h index 73c4ef8..f535ec8 100644 --- a/mercurialannotatejob.h +++ b/mercurialannotatejob.h @@ -1,81 +1,72 @@ /* This file is part of KDevelop Copyright 2016 Sergey Kalinichev This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #pragma once #include +#include "mercurialjob.h" #include "mercurialplugin.h" #include -class MercurialAnnotateJob : public KDevelop::VcsJob +class MercurialAnnotateJob : public MercurialJob { Q_OBJECT public: MercurialAnnotateJob(const QDir &workingDir, const KDevelop::VcsRevision& revision, const QUrl& location, MercurialPlugin *parent); void start() override; QVariant fetchResults() override; - KDevelop::VcsJob::JobStatus status() const override; - KDevelop::IPlugin *vcsPlugin() const override; - -protected: - bool doKill() override; private slots: void parseAnnotateOutput(KDevelop::VcsJob *job); void parseLogOutput(KDevelop::VcsJob *job); void parseCommitResult(KDevelop::VcsJob *job); void parseStripResult(KDevelop::VcsJob *job); void parseStatusResult(KDevelop::VcsJob *job); void subJobFinished(KJob *job); private: void launchAnnotateJob() const; - QDir m_workingDir; KDevelop::VcsRevision m_revision; QUrl m_location; - KDevelop::VcsJob::JobStatus m_status; - mutable QPointer m_job; QList m_annotations; QHash> m_revisionsCache; QSet m_revisionsToLog; bool m_hasModifiedFile = false; - void setFail(); - void setSuccess(); void nextPartOfLog(); // Those variables below for testing purposes only friend class MercurialTest; enum class TestCase { None, Status, Commit, Annotate, Log, Strip }; TestCase m_testCase = TestCase::None; }; diff --git a/mercurialjob.cpp b/mercurialjob.cpp new file mode 100644 index 0000000..eb5aeaf --- /dev/null +++ b/mercurialjob.cpp @@ -0,0 +1,75 @@ +/* + This file is part of KDevelop + + Copyright 2017 Sergey Kalinichev + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "mercurialjob.h" + +#include "mercurialplugin.h" + +using namespace KDevelop; + +MercurialJob::MercurialJob(const QDir& workingDir, MercurialPlugin* parent, JobType jobType) + : VcsJob(parent, OutputJob::Silent), + m_status(JobNotStarted), + m_workingDir(workingDir) +{ + setType(jobType); + setCapabilities(Killable); +} + +QVariant MercurialJob::fetchResults() +{ + return {}; +} + +VcsJob::JobStatus MercurialJob::status() const +{ + return m_status; +} + +KDevelop::IPlugin* MercurialJob::vcsPlugin() const +{ + return static_cast(parent()); +} + +bool MercurialJob::doKill() +{ + m_status = JobCanceled; + if (m_job) { + return m_job->kill(KJob::Quietly); + } + return true; +} + + +void MercurialJob::setFail() +{ + m_status = JobFailed; + emitResult(); + emit resultsReady(this); +} + + +void MercurialJob::setSuccess() +{ + m_status = JobSucceeded; + emitResult(); + emit resultsReady(this); +} diff --git a/mercurialjob.h b/mercurialjob.h new file mode 100644 index 0000000..16b45b1 --- /dev/null +++ b/mercurialjob.h @@ -0,0 +1,48 @@ +/* + This file is part of KDevelop + + Copyright 2017 Sergey Kalinichev + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#pragma once + +#include + +#include + +class MercurialPlugin; + +class MercurialJob : public KDevelop::VcsJob +{ +public: + MercurialJob(const QDir& workingDir, MercurialPlugin* parent, JobType jobType); + + QVariant fetchResults() override; + KDevelop::VcsJob::JobStatus status() const override; + KDevelop::IPlugin *vcsPlugin() const override; + +protected: + bool doKill() override; + + void setFail(); + void setSuccess(); + + KDevelop::VcsJob::JobStatus m_status; + mutable QPointer m_job; + QDir m_workingDir; +}; diff --git a/mercurialplugin.cpp b/mercurialplugin.cpp index 4d47a0a..dffd7c9 100644 --- a/mercurialplugin.cpp +++ b/mercurialplugin.cpp @@ -1,1171 +1,1125 @@ /*************************************************************************** * This file was taken from KDevelop's git plugin * * Copyright 2008 Evgeniy Ivanov * * * * Adapted for Mercurial * * Copyright 2009 Fabian Wiesel * * Copyright 2011 Andrey Batyiev * * * * 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 "mercurialplugin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include "mercurialjob.h" #include "mercurialvcslocationwidget.h" #include "mercurialannotatejob.h" #include "mercurialpushjob.h" #include "ui/mercurialheadswidget.h" #include "ui/mercurialqueuesmanager.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_MERCURIAL, "kdevplatform.plugins.mercurial") using namespace KDevelop; namespace { QString logTemplate() { return QStringLiteral("{desc}\\_%{date|rfc3339date}\\_%{author}\\_%{parents}\\_%{node}\\_%{rev}\\_%{file_dels}\\_%{file_adds}\\_%{file_mods}\\_%{file_copies}\\_%"); } } MercurialPlugin::MercurialPlugin(QObject *parent, const QVariantList &) : DistributedVersionControlPlugin(parent, QStringLiteral("kdevmercurial")) { if (QStandardPaths::findExecutable(QStringLiteral("hg")).isEmpty()) { setErrorDescription(i18n("Unable to find hg executable. Is it installed on the system?")); return; } m_headsAction = new QAction(i18n("Heads..."), this); m_mqNew = new QAction(i18nc("mercurial queues submenu", "New..."), this); m_mqPushAction = new QAction(i18nc("mercurial queues submenu", "Push"), this); m_mqPushAllAction = new QAction(i18nc("mercurial queues submenu", "Push All"), this); m_mqPopAction = new QAction(i18nc("mercurial queues submenu", "Pop"), this); m_mqPopAllAction = new QAction(i18nc("mercurial queues submenu", "Pop All"), this); m_mqManagerAction = new QAction(i18nc("mercurial queues submenu", "Manager..."), this); connect(m_headsAction, &QAction::triggered, this, &MercurialPlugin::showHeads); connect(m_mqManagerAction, &QAction::triggered, this, &MercurialPlugin::showMercurialQueuesManager); m_watcher = new KDirWatch(this); connect(m_watcher, &KDirWatch::dirty, this, &MercurialPlugin::fileChanged); connect(m_watcher, &KDirWatch::created, this, &MercurialPlugin::fileChanged); } MercurialPlugin::~MercurialPlugin() { } QString MercurialPlugin::name() const { return QLatin1String("Mercurial"); } void MercurialPlugin::fileChanged(const QString& file) { Q_ASSERT(file.endsWith(QStringLiteral("branch"))); const QUrl repoUrl = Path(file).parent().parent().toUrl(); m_branchesChange.append(repoUrl); QTimer::singleShot(1000, this, [this](){emit repositoryBranchChanged(m_branchesChange.takeFirst());}); } bool MercurialPlugin::isValidDirectory(const QUrl &directory) { // Mercurial uses the same test, so we don't lose any functionality static const QString hgDir(".hg"); if (m_lastRepoRoot.isParentOf(directory)) return true; const QString initialPath(directory.adjusted(QUrl::StripTrailingSlash).toLocalFile()); const QFileInfo finfo(initialPath); QDir dir; if (finfo.isFile()) { dir = finfo.absoluteDir(); } else { dir = QDir(initialPath); dir.makeAbsolute(); } while (!dir.cd(hgDir) && dir.cdUp()) {} // cdUp, until there is a sub-directory called .hg if (hgDir != dir.dirName()) return false; dir.cdUp(); // Leave .hg m_lastRepoRoot.setPath(dir.absolutePath()); return true; } bool MercurialPlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation) { Q_UNUSED(remoteLocation); // TODO return false; } bool MercurialPlugin::isVersionControlled(const QUrl &url) { const QFileInfo fsObject(url.toLocalFile()); if (!fsObject.isFile()) { return isValidDirectory(url); } DVcsJob *job = static_cast(status({url}, Recursive)); if (!job->exec()) { return false; } QList statuses = qvariant_cast >(job->fetchResults()); VcsStatusInfo info = qvariant_cast< VcsStatusInfo >(statuses.first()); if (info.state() == VcsStatusInfo::ItemAdded || info.state() == VcsStatusInfo::ItemModified || info.state() == VcsStatusInfo::ItemUpToDate) { return true; } return false; } VcsJob *MercurialPlugin::init(const QUrl &directory) { DVcsJob *job = new DVcsJob(directory.path(), this); *job << "hg" << "init"; return job; } VcsJob *MercurialPlugin::repositoryLocation(const QUrl &/*directory*/) { return nullptr; } VcsJob *MercurialPlugin::createWorkingCopy(const VcsLocation &localOrRepoLocationSrc, const QUrl &destinationDirectory, IBasicVersionControl::RecursionMode) { DVcsJob *job = new DVcsJob(QDir::home(), this, KDevelop::OutputJob::Silent); *job << "hg" << "clone" << "--" << localOrRepoLocationSrc.localUrl().toLocalFile() << destinationDirectory.toLocalFile(); return job; } VcsJob *MercurialPlugin::pull(const VcsLocation &otherRepository, const QUrl &workingRepository) { DVcsJob *job = new DVcsJob(findWorkingDir(workingRepository), this); *job << "hg" << "pull" << "--"; QString pathOrUrl = otherRepository.localUrl().toLocalFile(); if (!pathOrUrl.isEmpty()) *job << pathOrUrl; return job; } VcsJob *MercurialPlugin::push(const QUrl &workingRepository, const VcsLocation &otherRepository) { return new MercurialPushJob(findWorkingDir(workingRepository), otherRepository.localUrl(), this); } VcsJob *MercurialPlugin::add(const QList &localLocations, IBasicVersionControl::RecursionMode recursion) { QList locations = localLocations; if (recursion == NonRecursive) { filterOutDirectories(locations); } if (locations.empty()) { // nothing left after filtering return nullptr; } DVcsJob *job = new DVcsJob(findWorkingDir(locations.first()), this); *job << "hg" << "add" << "--" << locations; return job; } VcsJob *MercurialPlugin::copy(const QUrl &localLocationSrc, const QUrl &localLocationDst) { DVcsJob *job = new DVcsJob(findWorkingDir(localLocationSrc), this, KDevelop::OutputJob::Silent); *job << "hg" << "cp" << "--" << localLocationSrc.toLocalFile() << localLocationDst.path(); return job; } VcsJob *MercurialPlugin::move(const QUrl &localLocationSrc, const QUrl &localLocationDst) { DVcsJob *job = new DVcsJob(findWorkingDir(localLocationSrc), this, KDevelop::OutputJob::Silent); *job << "hg" << "mv" << "--" << localLocationSrc.toLocalFile() << localLocationDst.path(); return job; } //If no files specified then commit already added files VcsJob *MercurialPlugin::commit(const QString &message, const QList &localLocations, IBasicVersionControl::RecursionMode recursion) { QList locations = localLocations; if (recursion == NonRecursive) { filterOutDirectories(locations); } if (locations.empty() || message.isEmpty()) { return nullptr; } DVcsJob *job = new DVcsJob(findWorkingDir(locations.first()), this, KDevelop::OutputJob::Silent); *job << "hg" << "commit" << "-m" << message << "--" << locations; return job; } VcsJob *MercurialPlugin::update(const QList &localLocations, const VcsRevision &rev, IBasicVersionControl::RecursionMode recursion) { if (rev.revisionType() == VcsRevision::Special && rev.revisionValue().value() == VcsRevision::Head) { return pull(VcsLocation(), QUrl::fromLocalFile(findWorkingDir(localLocations.first()).path())); } QList locations = localLocations; if (recursion == NonRecursive) { filterOutDirectories(locations); } if (locations.empty()) { return nullptr; } DVcsJob *job = new DVcsJob(findWorkingDir(locations.first()), this); *job << "hg" << "revert" << "-r" << toMercurialRevision(rev) << "--" << locations; return job; } VcsJob *MercurialPlugin::resolve(const QList &files, KDevelop::IBasicVersionControl::RecursionMode recursion) { QList fileList = files; if (recursion == NonRecursive) { filterOutDirectories(fileList); } if (fileList.empty()) { return nullptr; } DVcsJob *job = new DVcsJob(findWorkingDir(fileList.first()), this); //Note: the message is quoted somewhere else, so if we quote here then we have quotes in the commit log *job << "hg" << "resolve" << "--" << fileList; return job; } VcsJob *MercurialPlugin::diff(const QUrl &fileOrDirectory, const VcsRevision &srcRevision, const VcsRevision &dstRevision, VcsDiff::Type diffType, IBasicVersionControl::RecursionMode recursionMode) { if (!fileOrDirectory.isLocalFile()) { return nullptr; } // non-standard searching for working directory QString workingDir; QFileInfo fileInfo(fileOrDirectory.toLocalFile()); // It's impossible to non-recursively diff directories if (recursionMode == NonRecursive && fileInfo.isDir()) { return nullptr; } // find out correct working directory if (fileInfo.isFile()) { workingDir = fileInfo.absolutePath(); } else { workingDir = fileInfo.absoluteFilePath(); } QString srcRev = toMercurialRevision(srcRevision); QString dstRev = toMercurialRevision(dstRevision); mercurialDebug() << "Diff between" << srcRevision.prettyValue() << '(' << srcRev << ')' << "and" << dstRevision.prettyValue() << '(' << dstRev << ')' << " requested"; if ( (srcRev.isNull() && dstRev.isNull()) || (srcRev.isEmpty() && dstRev.isEmpty()) ) { qWarning() << "Diff between" << srcRevision.prettyValue() << '(' << srcRev << ')' << "and" << dstRevision.prettyValue() << '(' << dstRev << ')' << " not possible"; return nullptr; } const QString srcPath = fileOrDirectory.toLocalFile(); DVcsJob *job = new DVcsJob(workingDir, this, KDevelop::OutputJob::Silent); *job << "hg" << "diff" << "-g"; // Hg cannot do a diff from // SomeRevision to Previous, or // Working to SomeRevsion directly, but the reverse if (dstRev.isNull() // Destination is "Previous" || (!srcRev.isNull() && srcRev.isEmpty()) // Source is "Working" ) { std::swap(srcRev, dstRev); *job << "--reverse"; } if (diffType == VcsDiff::DiffUnified) { *job << "-U" << "3"; // Default from GNU diff } if (srcRev.isNull() /* from "Previous" */ && dstRev.isEmpty() /* to "Working" */) { // Do nothing, that is the default } else if (srcRev.isNull()) { // Changes made in one arbitrary revision *job << "-c" << dstRev; } else { *job << "-r" << srcRev; if (!dstRev.isEmpty()) { *job << "-r" << dstRev; } } *job << "--" << srcPath; connect(job, &DVcsJob::readyForParsing, this, &MercurialPlugin::parseDiff); return job; } VcsJob *MercurialPlugin::remove(const QList &files) { if (files.empty()) { return nullptr; } DVcsJob *job = new DVcsJob(findWorkingDir(files.first()), this); *job << "hg" << "rm" << "--" << files; return job; } VcsJob *MercurialPlugin::status(const QList &localLocations, IBasicVersionControl::RecursionMode recursion) { QList locations = localLocations; if (recursion == NonRecursive) { filterOutDirectories(locations); } if (locations.empty()) { return nullptr; } DVcsJob *job = new DVcsJob(findWorkingDir(locations.first()), this, KDevelop::OutputJob::Silent); *job << "hg" << "status" << "-A" << "--" << locations; connect(job, &DVcsJob::readyForParsing, this, &MercurialPlugin::parseStatus); return job; } bool MercurialPlugin::parseStatus(DVcsJob *job) const { if (job->status() != VcsJob::JobSucceeded) { mercurialDebug() << "Job failed: " << job->output(); return false; } const QString dir = job->directory().absolutePath().append(QDir::separator()); mercurialDebug() << "Job succeeded for " << dir; const QStringList output = job->output().split('\n', QString::SkipEmptyParts); QList filestatus; QSet conflictedFiles; QStringList::const_iterator it = output.constBegin(); // FIXME: Revive this functionality and add tests for it! // conflicts first // for (; it != output.constEnd(); it++) { // QChar stCh = it->at(0); // if (stCh == '%') { // it++; // break; // } // // QUrl file = QUrl::fromLocalFile(it->mid(2).prepend(dir)); // // VcsStatusInfo status; // status.setUrl(file); // // FIXME: conflicts resolved // status.setState(VcsStatusInfo::ItemHasConflicts); // // conflictedFiles.insert(file); // filestatus.append(qVariantFromValue(status)); // } // standard statuses next for (; it != output.constEnd(); it++) { QChar stCh = it->at(0); QUrl file = QUrl::fromLocalFile(it->mid(2).prepend(dir)); if (!conflictedFiles.contains(file)) { VcsStatusInfo status; status.setUrl(file); status.setState(charToState(stCh.toLatin1())); filestatus.append(qVariantFromValue(status)); } } job->setResults(qVariantFromValue(filestatus)); return true; } VcsJob *MercurialPlugin::revert(const QList &localLocations, IBasicVersionControl::RecursionMode recursion) { QList locations = localLocations; if (recursion == NonRecursive) { filterOutDirectories(locations); } if (locations.empty()) { return nullptr; } DVcsJob *job = new DVcsJob(findWorkingDir(locations.first()), this); *job << "hg" << "revert" << "--" << locations; return job; } VcsJob *MercurialPlugin::log(const QUrl &localLocation, const VcsRevision &rev, unsigned long limit) { return log(localLocation, VcsRevision::createSpecialRevision(VcsRevision::Start), rev, limit); } VcsJob *MercurialPlugin::log(const QUrl &localLocation, const VcsRevision &rev, const VcsRevision &limit) { return log(localLocation, rev, limit, 0); } VcsJob *MercurialPlugin::log(const QUrl &localLocation, const VcsRevision &to, const VcsRevision &from, unsigned long limit) { DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this, KDevelop::OutputJob::Silent); *job << "hg" << "log" << "-r" << toMercurialRevision(from) + ':' + toMercurialRevision(to); if (limit > 0) *job << "-l" << QString::number(limit); *job << "--template" << logTemplate() << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &MercurialPlugin::parseLogOutputBasicVersionControl); return job; } VcsJob *MercurialPlugin::annotate(const QUrl &localLocation, const VcsRevision &rev) { return new MercurialAnnotateJob(findWorkingDir(localLocation), rev, localLocation, this); } -class MergeBranchJob : public VcsJob +class MergeBranchJob : public MercurialJob { public: MergeBranchJob(const QDir &workingDir, const QString& branchName, MercurialPlugin* parent) - : VcsJob(parent, KDevelop::OutputJob::Silent), - m_status(JobNotStarted), - m_workingDir(workingDir), + : MercurialJob(workingDir, parent, JobType::Merge), m_branchName(branchName) - { - setType(JobType::Merge); - setCapabilities(Killable); - } + {} void start() override { m_status = JobRunning; auto job = new DVcsJob(m_workingDir, vcsPlugin(), KDevelop::OutputJob::Silent); *job << "hg" << "log" << "-b" << m_branchName << "-l" << "1" << "--template" << "{rev}"; connect(job, &DVcsJob::resultsReady, this, [this](VcsJob* j) { auto job = static_cast(j); if (job->status() != VcsJob::JobSucceeded || job->output().isEmpty()) return setFail(); auto mergeJob = new DVcsJob(m_workingDir, vcsPlugin()); *mergeJob << "hg" << "merge" << "-r" << job->output().trimmed(); connect(mergeJob, &KJob::finished, this, [this](KJob *job) { if (job->error()) { setFail(); } }); connect(mergeJob, &DVcsJob::resultsReady, this, [this](VcsJob* j) { auto job = static_cast(j); if (job->status() != VcsJob::JobSucceeded || job->output().isEmpty()) return setFail(); setSuccess(); }); m_job = mergeJob; mergeJob->start(); }); connect(job, &KJob::finished, this, [this](KJob *job) { if (job->error()) { setFail(); } }); m_job = job; job->start(); } - QVariant fetchResults() override - { - return {}; - } - - KDevelop::VcsJob::JobStatus status() const override - { - return m_status; - } - - KDevelop::IPlugin *vcsPlugin() const override - { - return static_cast(parent()); - } - -protected: - bool doKill() override - { - m_status = JobCanceled; - if (m_job) { - return m_job->kill(KJob::Quietly); - } - return true; - } - private: - void setFail() - { - m_status = JobFailed; - emitResult(); - emit resultsReady(this); - } - - void setSuccess() - { - m_status = JobSucceeded; - emitResult(); - emit resultsReady(this); - } - - KDevelop::VcsJob::JobStatus m_status; - mutable QPointer m_job; - QDir m_workingDir; QString m_branchName; }; KDevelop::VcsJob* MercurialPlugin::mergeBranch(const QUrl& repository, const QString& branchName) { Q_ASSERT(!branchName.isEmpty()); return new MergeBranchJob(findWorkingDir(repository), branchName, this); } VcsJob *MercurialPlugin::heads(const QUrl &localLocation) { DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this, KDevelop::OutputJob::Silent); *job << "hg" << "heads" << "--template" << logTemplate(); connect(job, &DVcsJob::readyForParsing, this, &MercurialPlugin::parseLogOutputBasicVersionControl); return job; } VcsJob *MercurialPlugin::identify(const QUrl &localLocation) { DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this, KDevelop::OutputJob::Silent); *job << "hg" << "identify" << "-n"; connect(job, &DVcsJob::readyForParsing, this, &MercurialPlugin::parseIdentify); return job; } VcsJob *MercurialPlugin::checkoutHead(const QUrl &localLocation, const KDevelop::VcsRevision &rev) { DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); *job << "hg" << "update" << "-C" << toMercurialRevision(rev); return job; } VcsJob *MercurialPlugin::mergeWith(const QUrl &localLocation, const KDevelop::VcsRevision &rev) { DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); *job << "hg" << "merge" << "-r" << toMercurialRevision(rev); return job; } VcsJob *MercurialPlugin::branch(const QUrl &repository, const VcsRevision &/*rev*/, const QString &branchName) { DVcsJob *job = new DVcsJob(findWorkingDir(repository), this, OutputJob::Silent); *job << "hg" << "branch" << "--" << branchName; return job; } VcsJob *MercurialPlugin::branches(const QUrl &repository) { DVcsJob *job = new DVcsJob(findWorkingDir(repository), this, OutputJob::Silent); *job << "hg" << "branches"; connect(job, &DVcsJob::readyForParsing, this, &MercurialPlugin::parseMultiLineOutput); return job; } VcsJob *MercurialPlugin::currentBranch(const QUrl &repository) { DVcsJob *job = new DVcsJob(findWorkingDir(repository), this, OutputJob::Silent); *job << "hg" << "branch"; connect(job, &DVcsJob::readyForParsing, this, &MercurialPlugin::parseMultiLineOutput); return job; } VcsJob *MercurialPlugin::deleteBranch(const QUrl &/*repository*/, const QString &/*branchName*/) { // Not supported return nullptr; } VcsJob *MercurialPlugin::renameBranch(const QUrl &/*repository*/, const QString &/*oldBranchName*/, const QString &/*newBranchName*/) { // Not supported return nullptr; } VcsJob *MercurialPlugin::switchBranch(const QUrl &repository, const QString &branchName) { DVcsJob *job = new DVcsJob(findWorkingDir(repository), this); *job << "hg" << "update" << "--" << branchName; return job; } VcsJob *MercurialPlugin::tag(const QUrl &repository, const QString &commitMessage, const VcsRevision &rev, const QString &tagName) { DVcsJob *job = new DVcsJob(findWorkingDir(repository), this); *job << "hg" << "tag" << "-m" << commitMessage << "-r" << toMercurialRevision(rev) << "--" << tagName; return job; } QList MercurialPlugin::getAllCommits(const QString &repo) { DVcsJob *job = new DVcsJob(findWorkingDir(QUrl::fromLocalFile(repo)), this, OutputJob::Silent); *job << "hg" << "log" << "--template" << "{desc}\\_%{date|isodate}\\_%{author}\\_%{parents}\\_%{node}\\_%{rev}\\_%"; if (!job->exec() || job->status() != VcsJob::JobSucceeded) return QList(); QList commits; parseLogOutput(job, commits); return commits; } void MercurialPlugin::parseMultiLineOutput(DVcsJob *job) const { mercurialDebug() << job->output(); job->setResults(job->output().split('\n', QString::SkipEmptyParts)); } void MercurialPlugin::parseDiff(DVcsJob *job) { if (job->status() != VcsJob::JobSucceeded) { mercurialDebug() << "Parse-job failed: " << job->output(); return; } // Diffs are generated relativly to the repository root, // so we have recover it from the job. // Not quite clean m_lastRepoRoot holds the root, after querying isValidDirectory() QString workingDir(job->directory().absolutePath()); isValidDirectory(QUrl::fromLocalFile(workingDir)); QString repoRoot = m_lastRepoRoot.adjusted(QUrl::StripTrailingSlash).path(); VcsDiff diff; // We have to recover the type of the diff somehow. diff.setType(VcsDiff::DiffUnified); QString output = job->output(); // hg diff adds a prefix to the paths, which we will now strip QRegExp reg("(diff [^\n]+\n--- )a(/[^\n]+\n\\+\\+\\+ )b(/[^\n]+\n)"); QString replacement("\\1" + repoRoot + "\\2" + repoRoot + "\\3"); output.replace(reg, replacement); diff.setDiff(output); diff.setBaseDiff(QUrl::fromLocalFile(repoRoot)); diff.setDepth(0); job->setResults(qVariantFromValue(diff)); } void MercurialPlugin::parseLogOutputBasicVersionControl(DVcsJob *job) const { QList events; static unsigned int entriesPerCommit = 10; auto items = job->output().split("\\_%"); mercurialDebug() << items; /* remove trailing \\_% */ items.removeLast(); if ((items.count() % entriesPerCommit) != 0) { mercurialDebug() << "Cannot parse commit log: unexpected number of entries"; return; } /* get revision data from items. * because there is continuous stream of \\_% separated strings, * incrementation will occur inside loop body * * "{desc}\\_%{date|rfc3339date}\\_%{author}\\_%{parents}\\_%{node}\\_%{rev}\\_%" * "{file_dels}\\_%{file_adds}\\_%{file_mods}\\_%{file_copies}\\_%' */ for (auto it = items.constBegin(); it != items.constEnd();) { QString desc = *it++; mercurialDebug() << desc; Q_ASSERT(!desc.isEmpty()); QString date = *it++; mercurialDebug() << date; Q_ASSERT(!date.isEmpty()); QString author = *it++; mercurialDebug() << author; Q_ASSERT(!author.isEmpty()); QString parents = *it++; QString node = *it++; QString rev = *it++; mercurialDebug() << rev; Q_ASSERT(!rev.isEmpty()); VcsEvent event; event.setMessage(desc); event.setDate(QDateTime::fromString(date, Qt::ISODate)); event.setAuthor(author); VcsRevision revision; revision.setRevisionValue(rev.toLongLong(), KDevelop::VcsRevision::GlobalNumber); event.setRevision(revision); QList items; const VcsItemEvent::Action actions[3] = {VcsItemEvent::Deleted, VcsItemEvent::Added, VcsItemEvent::ContentsModified}; for (int i = 0; i < 3; i++) { const auto &files = *it++; if (files.isEmpty()) { continue; } foreach (const auto& file, files.split(' ')) { VcsItemEvent item; item.setActions(actions[i]); item.setRepositoryLocation(QUrl::fromPercentEncoding(file.toLocal8Bit())); items.push_back(item); } } const auto &copies = *it++; if (!copies.isEmpty()) { foreach (const auto & copy, copies.split(' ')) { auto files = copy.split('~'); // TODO: What is it? Why it doesn't work if (files.size() >= 2) { VcsItemEvent item; item.setActions(VcsItemEvent::Copied); item.setRepositoryCopySourceLocation(QUrl::fromPercentEncoding(files[0].toLocal8Bit())); item.setRepositoryLocation(QUrl::fromPercentEncoding(files[1].toLocal8Bit())); items.push_back(item); } } } event.setItems(items); events.push_back(QVariant::fromValue(event)); } job->setResults(QVariant(events)); } void MercurialPlugin::parseIdentify(DVcsJob *job) const { QString value = job->output(); QList result; mercurialDebug() << value; // remove last '\n' value.chop(1); // remove last '+' if necessary if (value.endsWith('+')) value.chop(1); foreach (const QString & rev, value.split('+')) { VcsRevision revision; revision.setRevisionValue(rev.toLongLong(), VcsRevision::GlobalNumber); result << qVariantFromValue(revision); } job->setResults(QVariant(result)); } void MercurialPlugin::parseLogOutput(const DVcsJob *job, QList &commits) const { mercurialDebug() << "parseLogOutput"; static unsigned int entriesPerCommit = 6; auto items = job->output().split("\\_%"); if (uint(items.size()) < entriesPerCommit || 1 != (items.size() % entriesPerCommit)) { mercurialDebug() << "Cannot parse commit log: unexpected number of entries"; return; } bool success = false; QString const &lastRev = items.at(entriesPerCommit - 1); unsigned int id = lastRev.toUInt(&success); if (!success) { mercurialDebug() << "Could not parse last revision \"" << lastRev << '"'; id = 1024; } QVector fullIds(id + 1); typedef std::reverse_iterator QStringListReverseIterator; QStringListReverseIterator rbegin(items.constEnd() - 1), rend(items.constBegin()); // Skip the final 0 unsigned int lastId; while (rbegin != rend) { QString rev = *rbegin++; QString node = *rbegin++; QString parents = *rbegin++; QString author = *rbegin++; QString date = *rbegin++; QString desc = *rbegin++; lastId = id; id = rev.toUInt(&success); if (!success) { mercurialDebug() << "Could not parse revision \"" << rev << '"'; return; } if (uint(fullIds.size()) <= id) { fullIds.resize(id * 2); } fullIds[id] = node; DVcsEvent commit; commit.setCommit(node); commit.setAuthor(author); commit.setDate(date); commit.setLog(desc); if (id == 0) { commit.setType(DVcsEvent::INITIAL); } else { if (parents.isEmpty() && id != 0) { commit.setParents(QStringList(fullIds[lastId])); } else { QStringList parentList; QStringList unparsedParentList = parents.split(QChar(' '), QString::SkipEmptyParts); // id:Short-node static const unsigned int shortNodeSuffixLen = 13; foreach (const QString & p, unparsedParentList) { QString ids = p.left(p.size() - shortNodeSuffixLen); id = ids.toUInt(&success); if (!success) { mercurialDebug() << "Could not parse parent-revision \"" << ids << "\" of revision " << rev; return; } parentList.push_back(fullIds[id]); } commit.setParents(parentList); } } commits.push_front(commit); } } VcsLocationWidget *MercurialPlugin::vcsLocation(QWidget *parent) const { return new MercurialVcsLocationWidget(parent); } void MercurialPlugin::filterOutDirectories(QList &locations) { QList fileLocations; foreach (const QUrl & location, locations) { if (!QFileInfo(location.toLocalFile()).isDir()) { fileLocations << location; } } locations = fileLocations; } VcsStatusInfo::State MercurialPlugin::charToState(const char ch) { switch (ch) { case 'M': return VcsStatusInfo::ItemModified; case 'A': return VcsStatusInfo::ItemAdded; case 'R': return VcsStatusInfo::ItemDeleted; case 'C': return VcsStatusInfo::ItemUpToDate; case '!': // Missing return VcsStatusInfo::ItemUserState; default: return VcsStatusInfo::ItemUnknown; } } QString MercurialPlugin::toMercurialRevision(const VcsRevision &vcsrev) { switch (vcsrev.revisionType()) { case VcsRevision::Special: switch (vcsrev.revisionValue().value()) { case VcsRevision::Head: return QString("tip"); case VcsRevision::Base: return QString("."); case VcsRevision::Working: return QString(""); case VcsRevision::Previous: // We can't determine this from one revision alone return QString::null; case VcsRevision::Start: return QString("0"); default: return QString(); } case VcsRevision::GlobalNumber: return QString::number(vcsrev.revisionValue().toLongLong()); case VcsRevision::Date: // TODO case VcsRevision::FileNumber: // No file number for mercurial default: return QString::null; } } QDir MercurialPlugin::findWorkingDir(const QUrl &location) { QFileInfo fileInfo(location.toLocalFile()); // find out correct working directory if (fileInfo.isFile()) { return fileInfo.absolutePath(); } else { return fileInfo.absoluteFilePath(); } } QUrl MercurialPlugin::remotePushRepositoryLocation(QDir &directory) { // check default-push first DVcsJob *job = new DVcsJob(directory, this); *job << "hg" << "paths" << "default-push"; if (!job->exec() || job->status() != VcsJob::JobSucceeded) { mercurialDebug() << "no default-push, hold on"; // or try default job = new DVcsJob(directory, this); *job << "hg" << "paths" << "default"; if (!job->exec() || job->status() != VcsJob::JobSucceeded) { mercurialDebug() << "nowhere to push!"; // fail is everywhere return QUrl(); } } // don't forget to strip last '\n' return QUrl::fromLocalFile(job->output().trimmed()); } void MercurialPlugin::registerRepositoryForCurrentBranchChanges(const QUrl& repository) { if (isValidDirectory(repository)) { QString headFile = m_lastRepoRoot.path() + QStringLiteral("/.hg/branch"); m_watcher->addFile(headFile); } } void MercurialPlugin::additionalMenuEntries(QMenu *menu, const QList &urls) { m_urls = urls; menu->addAction(m_headsAction); menu->addSeparator()->setText(i18n("Mercurial Queues")); menu->addAction(m_mqNew); menu->addAction(m_mqPushAction); menu->addAction(m_mqPushAllAction); menu->addAction(m_mqPopAction); menu->addAction(m_mqPopAllAction); menu->addAction(m_mqManagerAction); m_headsAction->setEnabled(m_urls.count() == 1); //FIXME:not supported yet, so disable m_mqNew->setEnabled(false); m_mqPushAction->setEnabled(false); m_mqPushAllAction->setEnabled(false); m_mqPopAction->setEnabled(false); m_mqPopAllAction->setEnabled(false); } void MercurialPlugin::showHeads() { const QUrl ¤t = m_urls.first(); MercurialHeadsWidget *headsWidget = new MercurialHeadsWidget(this, current); headsWidget->show(); } void MercurialPlugin::showMercurialQueuesManager() { const QUrl ¤t = m_urls.first(); MercurialQueuesManager *managerWidget = new MercurialQueuesManager(this, current); managerWidget->show(); } /* * Mercurial Queues */ VcsJob *MercurialPlugin::mqNew(const QUrl &/*localLocation*/, const QString &/*name*/, const QString &/*message*/) { return nullptr; } VcsJob *MercurialPlugin::mqPush(const QUrl &localLocation) { DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); *job << "hg" << "qpush"; return job; } VcsJob *MercurialPlugin::mqPushAll(const QUrl &localLocation) { DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); *job << "hg" << "qpush" << "-a"; return job; } VcsJob *MercurialPlugin::mqPop(const QUrl &localLocation) { DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); *job << "hg" << "qpop"; return job; } VcsJob *MercurialPlugin::mqPopAll(const QUrl &localLocation) { DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); *job << "hg" << "qpop" << "-a"; return job; } VcsJob *MercurialPlugin::mqApplied(const QUrl &localLocation) { DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); *job << "hg" << "qapplied"; connect(job, &DVcsJob::readyForParsing, this, &MercurialPlugin::parseMultiLineOutput); return job; } VcsJob *MercurialPlugin::mqUnapplied(const QUrl &localLocation) { DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); *job << "hg" << "qunapplied"; connect(job, &DVcsJob::readyForParsing, this, &MercurialPlugin::parseMultiLineOutput); return job; } // #include "mercurialplugin.moc" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ee0211b..d0f027d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,32 +1,33 @@ # Due to the use of system() and some unix-style paths this test will only run # under Linux. (Maybe this can be fixed later) # # Moreover, I'm not sure if there is a cvs commandline client for windows # (need to check this out ...) if (UNIX) # Running the test only makes sense if the mercurial command line client # is present. So check for it before adding the test... find_program(MERCURIAL NAMES hg) if (MERCURIAL) set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) set(mercurialTest_SRCS test_mercurial.cpp ../ui/mercurialqueuesmanager.cpp ../ui/mercurialheadswidget.cpp ../models/mercurialqueueseriesmodel.cpp ../models/mercurialheadsmodel.cpp ../mercurialannotatejob.cpp ../mercurialpushjob.cpp ../mercurialvcslocationwidget.cpp - ../mercurialplugin.cpp) + ../mercurialplugin.cpp + ../mercurialjob.cpp) ecm_add_test(${mercurialTest_SRCS} TEST_NAME test_kdevmercurial LINK_LIBRARIES Qt5::Test KDev::Vcs KDev::Util KDev::Tests GUI ) endif (MERCURIAL) endif (UNIX)