File indexing completed on 2024-05-05 04:39:01

0001 /*
0002     SPDX-FileCopyrightText: 2013-2014 Maciej Poleski
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "bazaarplugin.h"
0008 
0009 #include <QDir>
0010 #include <QMenu>
0011 #include <QStandardPaths>
0012 
0013 #include <KLocalizedString>
0014 
0015 #include <vcs/widgets/standardvcslocationwidget.h>
0016 #include <vcs/dvcs/dvcsjob.h>
0017 #include <vcs/vcsstatusinfo.h>
0018 #include <vcs/vcslocation.h>
0019 #include <vcs/dvcs/ui/dvcsimportmetadatawidget.h>
0020 #include <interfaces/contextmenuextension.h>
0021 #include <interfaces/context.h>
0022 
0023 #include "bazaarutils.h"
0024 #include "bzrannotatejob.h"
0025 #include "copyjob.h"
0026 #include "diffjob.h"
0027 
0028 using namespace KDevelop;
0029 
0030 BazaarPlugin::BazaarPlugin(QObject* parent, const QVariantList& args) :
0031     IPlugin(QStringLiteral("kdevbazaar"), parent),
0032     m_vcsPluginHelper(new KDevelop::VcsPluginHelper(this, this))
0033 {
0034     Q_UNUSED(args); // What is this?
0035     if (QStandardPaths::findExecutable(QStringLiteral("bzr")).isEmpty()) {
0036         setErrorDescription(i18n("Unable to find Bazaar (bzr) executable. Is it installed on the system?"));
0037         return;
0038     }
0039 
0040     setObjectName(QStringLiteral("Bazaar"));
0041 }
0042 
0043 BazaarPlugin::~BazaarPlugin()
0044 {
0045 }
0046 
0047 QString BazaarPlugin::name() const
0048 {
0049     return QStringLiteral("Bazaar");
0050 }
0051 
0052 bool BazaarPlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation)
0053 {
0054     const QString scheme = remoteLocation.scheme();
0055     if (scheme == QLatin1String("bzr") ||
0056         scheme == QLatin1String("bzr+ssh") ||
0057         scheme == QLatin1String("lp")) {
0058         return true;
0059     }
0060     return false;
0061 }
0062 
0063 VcsJob* BazaarPlugin::add(const QList<QUrl>& localLocations, IBasicVersionControl::RecursionMode recursion)
0064 {
0065     auto* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this);
0066     job->setType(VcsJob::Add);
0067     *job << "bzr" << "add";
0068     if(recursion == NonRecursive)
0069         *job << "--no-recurse";
0070     *job << localLocations;
0071     return job;
0072 }
0073 
0074 VcsJob* BazaarPlugin::annotate(const QUrl& localLocation, const VcsRevision& rev)
0075 {
0076     VcsJob* job = new BzrAnnotateJob(BazaarUtils::workingCopy(localLocation), BazaarUtils::getRevisionSpec(rev), localLocation, this, KDevelop::OutputJob::Silent);
0077     return job;
0078 }
0079 
0080 VcsJob* BazaarPlugin::commit(const QString& message, const QList<QUrl>& localLocations, IBasicVersionControl::RecursionMode recursion)
0081 {
0082     QDir dir = BazaarUtils::workingCopy(localLocations[0]);
0083     auto* job = new DVcsJob(dir, this);
0084     job->setType(VcsJob::Commit);
0085 
0086     *job << "bzr" << "commit" << BazaarUtils::handleRecursion(localLocations, recursion) << "-m" << message;
0087     return job;
0088 }
0089 
0090 VcsJob* BazaarPlugin::copy(const QUrl& localLocationSrc, const QUrl& localLocationDstn)
0091 {
0092     return new CopyJob(localLocationSrc, localLocationDstn, this);
0093 }
0094 
0095 VcsImportMetadataWidget* BazaarPlugin::createImportMetadataWidget(QWidget* parent)
0096 {
0097     return new DvcsImportMetadataWidget(parent);
0098 }
0099 
0100 VcsJob* BazaarPlugin::createWorkingCopy(const VcsLocation& sourceRepository, const QUrl& destinationDirectory, IBasicVersionControl::RecursionMode recursion)
0101 {
0102     Q_UNUSED(recursion);
0103     // What is the purpose of recursion parameter?
0104     auto* job = new DVcsJob(BazaarUtils::toQDir(sourceRepository.localUrl()), this);
0105     job->setType(VcsJob::Import);
0106     *job << "bzr" << "branch" << sourceRepository.localUrl().url() << destinationDirectory;
0107     return job;
0108 }
0109 
0110 VcsJob* BazaarPlugin::diff(const QUrl& fileOrDirectory, const VcsRevision& srcRevision, const VcsRevision& dstRevision, IBasicVersionControl::RecursionMode recursion)
0111 {
0112     Q_UNUSED(recursion);
0113     VcsJob* job = new DiffJob(BazaarUtils::workingCopy(fileOrDirectory), BazaarUtils::getRevisionSpecRange(srcRevision, dstRevision), fileOrDirectory, this);
0114     return job;
0115 }
0116 
0117 VcsJob* BazaarPlugin::init(const QUrl& localRepositoryRoot)
0118 {
0119     auto* job = new DVcsJob(BazaarUtils::toQDir(localRepositoryRoot), this);
0120     job->setType(VcsJob::Import);
0121     *job << "bzr" << "init";
0122     return job;
0123 }
0124 
0125 bool BazaarPlugin::isVersionControlled(const QUrl& localLocation)
0126 {
0127     QDir workCopy = BazaarUtils::workingCopy(localLocation);
0128     auto* job = new DVcsJob(workCopy, this, OutputJob::Silent);
0129     job->setType(VcsJob::Unknown);
0130     job->setIgnoreError(true);
0131     *job << "bzr" << "ls" << "--from-root" << "-R" << "-V";
0132     job->exec();
0133     if (job->status() == VcsJob::JobSucceeded) {
0134         QList<QFileInfo> filesAndDirectoriesList;
0135         const auto output = job->output().split(QLatin1Char('\n'));
0136         filesAndDirectoriesList.reserve(output.size());
0137         const QChar dirSeparator = QDir::separator();
0138         for (const auto& fod : output) {
0139             filesAndDirectoriesList.append(QFileInfo(workCopy.absolutePath() + dirSeparator + fod));
0140         }
0141         QFileInfo fi(localLocation.toLocalFile());
0142         if (fi.isDir() || fi.isFile()) {
0143             QFileInfo file(localLocation.toLocalFile());
0144             return filesAndDirectoriesList.contains(file);
0145         }
0146     }
0147     return false;
0148 }
0149 
0150 VcsJob* BazaarPlugin::log(const QUrl& localLocation, const VcsRevision& rev, long unsigned int limit)
0151 {
0152     auto* job = new DVcsJob(BazaarUtils::workingCopy(localLocation), this);
0153     job->setType(VcsJob::Log);
0154     *job << "bzr" << "log" << "--long" << "-v" << localLocation << BazaarUtils::getRevisionSpecRange(rev) << "-l" << QString::number(limit);
0155     connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrLog);
0156     return job;
0157 }
0158 
0159 VcsJob* BazaarPlugin::log(const QUrl& localLocation, const VcsRevision& rev, const VcsRevision& limit)
0160 {
0161     auto* job = new DVcsJob(BazaarUtils::workingCopy(localLocation), this);
0162     job->setType(VcsJob::Log);
0163     *job << "bzr" << "log" << "--long" << "-v" << localLocation << BazaarUtils::getRevisionSpecRange(limit, rev);
0164     connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrLog);
0165     return job;
0166 }
0167 
0168 void BazaarPlugin::parseBzrLog(DVcsJob* job)
0169 {
0170     QVariantList result;
0171     const auto parts = job->output().split(
0172         QStringLiteral("------------------------------------------------------------"), Qt::SkipEmptyParts);
0173     for (const QString& part : parts) {
0174         auto event = BazaarUtils::parseBzrLogPart(part);
0175         if (event.revision().revisionType() != VcsRevision::Invalid)
0176             result.append(QVariant::fromValue(event));
0177     }
0178     job->setResults(result);
0179 }
0180 
0181 VcsJob* BazaarPlugin::move(const QUrl& localLocationSrc, const QUrl& localLocationDst)
0182 {
0183     auto* job = new DVcsJob(BazaarUtils::workingCopy(localLocationSrc), this);
0184     job->setType(VcsJob::JobType::Move);
0185     *job << "bzr" << "move" << localLocationSrc << localLocationDst;
0186     return job;
0187 }
0188 
0189 VcsJob* BazaarPlugin::pull(const VcsLocation& localOrRepoLocationSrc, const QUrl& localRepositoryLocation)
0190 {
0191     // API describes hg pull which is git fetch equivalent
0192     // bzr has pull, but it succeeds only if fast-forward is possible
0193     // in other cases bzr merge should be used instead (bzr pull would fail)
0194     // Information about repository must be provided at least once.
0195     auto* job = new DVcsJob(BazaarUtils::workingCopy(localRepositoryLocation), this);
0196     job->setType(VcsJob::JobType::Pull);
0197     *job << "bzr" << "pull";
0198     if (!localOrRepoLocationSrc.localUrl().isEmpty()) {
0199         *job << localOrRepoLocationSrc.localUrl();
0200     }
0201     // localUrl always makes sense. Even on remote repositories which are handled
0202     // transparently.
0203     return job;
0204 }
0205 
0206 VcsJob* BazaarPlugin::push(const QUrl& localRepositoryLocation, const VcsLocation& localOrRepoLocationDst)
0207 {
0208     auto* job = new DVcsJob(BazaarUtils::workingCopy(localRepositoryLocation), this);
0209     job->setType(VcsJob::JobType::Push);
0210     *job << "bzr" << "push" << localOrRepoLocationDst.localUrl();
0211     // localUrl always makes sense. Even on remote repositories which are handled
0212     // transparently.
0213     return job;
0214 }
0215 
0216 VcsJob* BazaarPlugin::remove(const QList<QUrl>& localLocations)
0217 {
0218     auto* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this);
0219     job->setType(VcsJob::JobType::Remove);
0220     *job << "bzr" << "remove" << localLocations;
0221     return job;
0222 }
0223 
0224 VcsJob* BazaarPlugin::repositoryLocation(const QUrl& localLocation)
0225 {
0226     auto* job = new DVcsJob(BazaarUtils::workingCopy(localLocation), this);
0227     job->setType(VcsJob::JobType::Unknown);
0228     *job << "bzr" << "root" << localLocation;   // It is only to make sure
0229     connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrRoot);
0230     return job;
0231 }
0232 
0233 void BazaarPlugin::parseBzrRoot(DVcsJob* job)
0234 {
0235     QString filename = job->dvcsCommand().at(2);
0236     QString rootDirectory = job->output();
0237     QString localFilename = QFileInfo(QUrl::fromLocalFile(filename).toLocalFile()).absoluteFilePath();
0238     QString result = localFilename.mid(localFilename.indexOf(rootDirectory) + rootDirectory.length());
0239     job->setResults(QVariant::fromValue(result));
0240 }
0241 
0242 VcsJob* BazaarPlugin::resolve(const QList<QUrl>& localLocations, IBasicVersionControl::RecursionMode recursion)
0243 {
0244     return add(localLocations, recursion);
0245     // How to provide "a conflict solving dialog to the user"?
0246     // In any case this plugin is unable to make any conflict.
0247 }
0248 
0249 VcsJob* BazaarPlugin::revert(const QList<QUrl>& localLocations, IBasicVersionControl::RecursionMode recursion)
0250 {
0251     auto* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this);
0252     job->setType(VcsJob::JobType::Revert);
0253     *job << "bzr" << "revert" << BazaarUtils::handleRecursion(localLocations, recursion);
0254     return job;
0255 }
0256 
0257 VcsJob* BazaarPlugin::status(const QList<QUrl>& localLocations, IBasicVersionControl::RecursionMode recursion)
0258 {
0259     Q_UNUSED(recursion);
0260     auto* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this);
0261     job->setType(VcsJob::Status);
0262     *job << "bzr" << "status" << "--short" << "--no-pending" << "--no-classify" << localLocations;
0263     connect(job, &DVcsJob::readyForParsing, this, &BazaarPlugin::parseBzrStatus);
0264     return job;
0265 }
0266 
0267 void BazaarPlugin::parseBzrStatus(DVcsJob* job)
0268 {
0269     QVariantList result;
0270     QSet<QString> filesWithStatus;
0271     QDir workingCopy = job->directory();
0272     const auto output = job->output().split(QLatin1Char('\n'));
0273     result.reserve(output.size());
0274     for (const auto& line : output) {
0275         auto status = BazaarUtils::parseVcsStatusInfoLine(line);
0276         result.append(QVariant::fromValue(status));
0277         filesWithStatus.insert(BazaarUtils::concatenatePath(workingCopy, status.url()));
0278     }
0279 
0280     QStringList command = job->dvcsCommand();
0281     for (auto it = command.constBegin() + command.indexOf(QStringLiteral("--no-classify")) + 1, itEnd = command.constEnd(); it != itEnd; ++it) {
0282         QString path = QFileInfo(*it).absoluteFilePath();
0283         if (!filesWithStatus.contains(path)) {
0284             filesWithStatus.insert(path);
0285             KDevelop::VcsStatusInfo status;
0286             status.setState(VcsStatusInfo::ItemUpToDate);
0287             status.setUrl(QUrl::fromLocalFile(*it));
0288             result.append(QVariant::fromValue(status));
0289         }
0290     }
0291 
0292     job->setResults(result);
0293 }
0294 
0295 VcsJob* BazaarPlugin::update(const QList<QUrl>& localLocations, const VcsRevision& rev, IBasicVersionControl::RecursionMode recursion)
0296 {
0297     // bzr update is stronger than API (it's effectively merge)
0298     // the best approximation is bzr pull
0299     auto* job = new DVcsJob(BazaarUtils::workingCopy(localLocations[0]), this);
0300     Q_UNUSED(recursion);
0301     // recursion and file locations are ignored - we can update only whole
0302     // working copy
0303     job->setType(VcsJob::JobType::Update);
0304     *job << "bzr" << "pull" << BazaarUtils::getRevisionSpec(rev);
0305     return job;
0306 }
0307 
0308 VcsLocationWidget* BazaarPlugin::vcsLocation(QWidget* parent) const
0309 {
0310     return new KDevelop::StandardVcsLocationWidget(parent);
0311 }
0312 
0313 ContextMenuExtension BazaarPlugin::contextMenuExtension(Context* context, QWidget* parent)
0314 {
0315     m_vcsPluginHelper->setupFromContext(context);
0316     QList<QUrl> const& ctxUrlList = m_vcsPluginHelper->contextUrlList();
0317 
0318     bool isWorkingDirectory = false;
0319     for (const QUrl & url : ctxUrlList) {
0320         if (BazaarUtils::isValidDirectory(url)) {
0321             isWorkingDirectory = true;
0322             break;
0323         }
0324     }
0325 
0326     if (!isWorkingDirectory) { // Not part of a repository
0327         return ContextMenuExtension();
0328     }
0329 
0330     QMenu* menu = m_vcsPluginHelper->commonActions(parent);
0331 
0332     ContextMenuExtension menuExt;
0333     menuExt.addAction(ContextMenuExtension::VcsGroup, menu->menuAction());
0334 
0335     return menuExt;
0336 }
0337 
0338 #include "moc_bazaarplugin.cpp"