File indexing completed on 2024-05-05 16:44:43
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"