File indexing completed on 2024-04-28 08:25:31
0001 /*************************************************************************** 0002 * This file was taken from KDevelop's git plugin * 0003 * Copyright 2008 Evgeniy Ivanov <powerfox@kde.ru> * 0004 * * 0005 * Adapted for Mercurial * 0006 * Copyright 2009 Fabian Wiesel <fabian.wiesel@fu-berlin.de> * 0007 * Copyright 2011 Andrey Batyiev <batyiev@gmail.com> * 0008 * * 0009 * This program is free software; you can redistribute it and/or * 0010 * modify it under the terms of the GNU General Public License as * 0011 * published by the Free Software Foundation; either version 2 of * 0012 * the License or (at your option) version 3 or any later version * 0013 * accepted by the membership of KDE e.V. (or its successor approved * 0014 * by the membership of KDE e.V.), which shall act as a proxy * 0015 * defined in Section 14 of version 3 of the license. * 0016 * * 0017 * This program is distributed in the hope that it will be useful, * 0018 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0019 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0020 * GNU General Public License for more details. * 0021 * * 0022 * You should have received a copy of the GNU General Public License * 0023 * along with this program. If not, see <http://www.gnu.org/licenses/>. * 0024 ***************************************************************************/ 0025 0026 #include "mercurialplugin.h" 0027 0028 #include <algorithm> 0029 #include <memory> 0030 0031 #include <QDir> 0032 #include <QDateTime> 0033 #include <QFileInfo> 0034 #include <QAction> 0035 #include <QMenu> 0036 #include <QDateTime> 0037 #include <QStandardPaths> 0038 #include <QTimer> 0039 0040 #include <KDirWatch> 0041 0042 #include <interfaces/icore.h> 0043 0044 #include <vcs/vcsjob.h> 0045 #include <vcs/vcsevent.h> 0046 #include <vcs/vcsrevision.h> 0047 #include <vcs/vcsannotation.h> 0048 #include <vcs/vcslocation.h> 0049 #include <vcs/dvcs/dvcsjob.h> 0050 0051 #include <util/path.h> 0052 0053 #include "mercurialjob.h" 0054 #include "mercurialvcslocationwidget.h" 0055 #include "mercurialannotatejob.h" 0056 #include "mercurialpushjob.h" 0057 #include "ui/mercurialheadswidget.h" 0058 #include "ui/mercurialqueuesmanager.h" 0059 0060 #include "debug.h" 0061 0062 using namespace KDevelop; 0063 0064 namespace 0065 { 0066 QString logTemplate() 0067 { 0068 return QStringLiteral("{desc}\\_%{date|rfc3339date}\\_%{author}\\_%{parents}\\_%{node}\\_%{rev}\\_%{file_dels}\\_%{file_adds}\\_%{file_mods}\\_%{file_copies}\\_%"); 0069 } 0070 } 0071 0072 MercurialPlugin::MercurialPlugin(QObject *parent, const QVariantList &) 0073 : DistributedVersionControlPlugin(parent, QStringLiteral("kdevmercurial")) 0074 { 0075 if (QStandardPaths::findExecutable(QStringLiteral("hg")).isEmpty()) { 0076 setErrorDescription(i18n("Unable to find hg executable. Is it installed on the system?")); 0077 return; 0078 } 0079 0080 m_headsAction = new QAction(i18n("Heads..."), this); 0081 m_mqNew = new QAction(i18nc("mercurial queues submenu", "New..."), this); 0082 m_mqPushAction = new QAction(i18nc("mercurial queues submenu", "Push"), this); 0083 m_mqPushAllAction = new QAction(i18nc("mercurial queues submenu", "Push All"), this); 0084 m_mqPopAction = new QAction(i18nc("mercurial queues submenu", "Pop"), this); 0085 m_mqPopAllAction = new QAction(i18nc("mercurial queues submenu", "Pop All"), this); 0086 m_mqManagerAction = new QAction(i18nc("mercurial queues submenu", "Manager..."), this); 0087 0088 connect(m_headsAction, &QAction::triggered, this, &MercurialPlugin::showHeads); 0089 connect(m_mqManagerAction, &QAction::triggered, this, &MercurialPlugin::showMercurialQueuesManager); 0090 0091 m_watcher = new KDirWatch(this); 0092 connect(m_watcher, &KDirWatch::dirty, this, &MercurialPlugin::fileChanged); 0093 connect(m_watcher, &KDirWatch::created, this, &MercurialPlugin::fileChanged); 0094 } 0095 0096 MercurialPlugin::~MercurialPlugin() 0097 { 0098 } 0099 0100 QString MercurialPlugin::name() const 0101 { 0102 return QLatin1String("Mercurial"); 0103 } 0104 0105 void MercurialPlugin::fileChanged(const QString& file) 0106 { 0107 Q_ASSERT(file.endsWith(QStringLiteral("branch"))); 0108 const QUrl repoUrl = Path(file).parent().parent().toUrl(); 0109 0110 m_branchesChange.append(repoUrl); 0111 QTimer::singleShot(1000, this, [this](){emit repositoryBranchChanged(m_branchesChange.takeFirst());}); 0112 } 0113 0114 bool MercurialPlugin::isValidDirectory(const QUrl &directory) 0115 { 0116 // Mercurial uses the same test, so we don't lose any functionality 0117 static const QString hgDir(".hg"); 0118 0119 if (m_lastRepoRoot.isParentOf(directory)) 0120 return true; 0121 0122 const QString initialPath(directory.adjusted(QUrl::StripTrailingSlash).toLocalFile()); 0123 const QFileInfo finfo(initialPath); 0124 QDir dir; 0125 if (finfo.isFile()) { 0126 dir = finfo.absoluteDir(); 0127 } else { 0128 dir = QDir(initialPath); 0129 dir.makeAbsolute(); 0130 } 0131 0132 while (!dir.cd(hgDir) && dir.cdUp()) 0133 {} // cdUp, until there is a sub-directory called .hg 0134 0135 if (hgDir != dir.dirName()) 0136 return false; 0137 0138 dir.cdUp(); // Leave .hg 0139 m_lastRepoRoot.setPath(dir.absolutePath()); 0140 return true; 0141 } 0142 0143 bool MercurialPlugin::isValidRemoteRepositoryUrl(const QUrl& remoteLocation) 0144 { 0145 Q_UNUSED(remoteLocation); 0146 // TODO 0147 return false; 0148 } 0149 0150 bool MercurialPlugin::isVersionControlled(const QUrl &url) 0151 { 0152 const QFileInfo fsObject(url.toLocalFile()); 0153 0154 if (!fsObject.isFile()) { 0155 return isValidDirectory(url); 0156 } 0157 0158 DVcsJob *job = static_cast<DVcsJob *>(status({url}, Recursive)); 0159 if (!job->exec()) { 0160 return false; 0161 } 0162 0163 QList<QVariant> statuses = qvariant_cast<QList<QVariant> >(job->fetchResults()); 0164 VcsStatusInfo info = qvariant_cast< VcsStatusInfo >(statuses.first()); 0165 if (info.state() == VcsStatusInfo::ItemAdded || 0166 info.state() == VcsStatusInfo::ItemModified || 0167 info.state() == VcsStatusInfo::ItemUpToDate) { 0168 return true; 0169 } 0170 0171 return false; 0172 } 0173 0174 VcsJob *MercurialPlugin::init(const QUrl &directory) 0175 { 0176 DVcsJob *job = new DVcsJob(directory.path(), this); 0177 0178 *job << "hg" << "init"; 0179 0180 return job; 0181 } 0182 0183 VcsJob *MercurialPlugin::repositoryLocation(const QUrl &/*directory*/) 0184 { 0185 return nullptr; 0186 } 0187 0188 VcsJob *MercurialPlugin::createWorkingCopy(const VcsLocation &localOrRepoLocationSrc, const QUrl &destinationDirectory, IBasicVersionControl::RecursionMode) 0189 { 0190 DVcsJob *job = new DVcsJob(QDir::home(), this, KDevelop::OutputJob::Silent); 0191 0192 *job << "hg" << "clone" << "--" << localOrRepoLocationSrc.localUrl().toLocalFile() << destinationDirectory.toLocalFile(); 0193 0194 return job; 0195 } 0196 0197 VcsJob *MercurialPlugin::pull(const VcsLocation &otherRepository, const QUrl &workingRepository) 0198 { 0199 DVcsJob *job = new DVcsJob(findWorkingDir(workingRepository), this); 0200 0201 *job << "hg" << "pull" << "--"; 0202 0203 QString pathOrUrl = otherRepository.localUrl().toLocalFile(); 0204 0205 if (!pathOrUrl.isEmpty()) 0206 *job << pathOrUrl; 0207 0208 return job; 0209 } 0210 0211 VcsJob *MercurialPlugin::push(const QUrl &workingRepository, const VcsLocation &otherRepository) 0212 { 0213 return new MercurialPushJob(findWorkingDir(workingRepository), otherRepository.localUrl(), this); 0214 } 0215 0216 VcsJob *MercurialPlugin::add(const QList<QUrl> &localLocations, IBasicVersionControl::RecursionMode recursion) 0217 { 0218 QList<QUrl> locations = localLocations; 0219 0220 if (recursion == NonRecursive) { 0221 filterOutDirectories(locations); 0222 } 0223 0224 if (locations.empty()) { 0225 // nothing left after filtering 0226 return nullptr; 0227 } 0228 0229 DVcsJob *job = new DVcsJob(findWorkingDir(locations.first()), this); 0230 *job << "hg" << "add" << "--" << locations; 0231 return job; 0232 } 0233 0234 VcsJob *MercurialPlugin::copy(const QUrl &localLocationSrc, const QUrl &localLocationDst) 0235 { 0236 DVcsJob *job = new DVcsJob(findWorkingDir(localLocationSrc), this, KDevelop::OutputJob::Silent); 0237 *job << "hg" << "cp" << "--" << localLocationSrc.toLocalFile() << localLocationDst.path(); 0238 return job; 0239 } 0240 0241 VcsJob *MercurialPlugin::move(const QUrl &localLocationSrc, 0242 const QUrl &localLocationDst) 0243 { 0244 DVcsJob *job = new DVcsJob(findWorkingDir(localLocationSrc), this, KDevelop::OutputJob::Silent); 0245 *job << "hg" << "mv" << "--" << localLocationSrc.toLocalFile() << localLocationDst.path(); 0246 return job; 0247 } 0248 0249 //If no files specified then commit already added files 0250 VcsJob *MercurialPlugin::commit(const QString &message, 0251 const QList<QUrl> &localLocations, 0252 IBasicVersionControl::RecursionMode recursion) 0253 { 0254 QList<QUrl> locations = localLocations; 0255 0256 if (recursion == NonRecursive) { 0257 filterOutDirectories(locations); 0258 } 0259 0260 if (locations.empty() || message.isEmpty()) { 0261 return nullptr; 0262 } 0263 0264 DVcsJob *job = new DVcsJob(findWorkingDir(locations.first()), this, KDevelop::OutputJob::Silent); 0265 *job << "hg" << "commit" << "-m" << message << "--" << locations; 0266 return job; 0267 } 0268 0269 VcsJob *MercurialPlugin::update(const QList<QUrl> &localLocations, 0270 const VcsRevision &rev, 0271 IBasicVersionControl::RecursionMode recursion) 0272 { 0273 if (rev.revisionType() == VcsRevision::Special && 0274 rev.revisionValue().value<VcsRevision::RevisionSpecialType>() == VcsRevision::Head) { 0275 return pull(VcsLocation(), QUrl::fromLocalFile(findWorkingDir(localLocations.first()).path())); 0276 } 0277 0278 QList<QUrl> locations = localLocations; 0279 0280 if (recursion == NonRecursive) { 0281 filterOutDirectories(locations); 0282 } 0283 0284 if (locations.empty()) { 0285 return nullptr; 0286 } 0287 0288 DVcsJob *job = new DVcsJob(findWorkingDir(locations.first()), this); 0289 0290 0291 *job << "hg" << "revert" << "-r" << toMercurialRevision(rev) << "--" << locations; 0292 return job; 0293 } 0294 0295 VcsJob *MercurialPlugin::resolve(const QList<QUrl> &files, KDevelop::IBasicVersionControl::RecursionMode recursion) 0296 { 0297 QList<QUrl> fileList = files; 0298 if (recursion == NonRecursive) { 0299 filterOutDirectories(fileList); 0300 } 0301 0302 if (fileList.empty()) { 0303 return nullptr; 0304 } 0305 0306 DVcsJob *job = new DVcsJob(findWorkingDir(fileList.first()), this); 0307 0308 //Note: the message is quoted somewhere else, so if we quote here then we have quotes in the commit log 0309 *job << "hg" << "resolve" << "--" << fileList; 0310 0311 return job; 0312 } 0313 0314 VcsJob *MercurialPlugin::diff(const QUrl &fileOrDirectory, 0315 const VcsRevision &srcRevision, 0316 const VcsRevision &dstRevision, 0317 IBasicVersionControl::RecursionMode recursionMode) 0318 { 0319 if (!fileOrDirectory.isLocalFile()) { 0320 return nullptr; 0321 } 0322 0323 // non-standard searching for working directory 0324 QString workingDir; 0325 QFileInfo fileInfo(fileOrDirectory.toLocalFile()); 0326 0327 // It's impossible to non-recursively diff directories 0328 if (recursionMode == NonRecursive && fileInfo.isDir()) { 0329 return nullptr; 0330 } 0331 0332 // find out correct working directory 0333 if (fileInfo.isFile()) { 0334 workingDir = fileInfo.absolutePath(); 0335 } else { 0336 workingDir = fileInfo.absoluteFilePath(); 0337 } 0338 0339 QString srcRev = toMercurialRevision(srcRevision); 0340 QString dstRev = toMercurialRevision(dstRevision); 0341 0342 qCDebug(PLUGIN_MERCURIAL) << "Diff between" << srcRevision.prettyValue() << '(' << srcRev << ')' << "and" << dstRevision.prettyValue() << '(' << dstRev << ')' << " requested"; 0343 0344 if ( 0345 (srcRev.isNull() && dstRev.isNull()) || 0346 (srcRev.isEmpty() && dstRev.isEmpty()) 0347 ) { 0348 qWarning() << "Diff between" << srcRevision.prettyValue() << '(' << srcRev << ')' << "and" << dstRevision.prettyValue() << '(' << dstRev << ')' << " not possible"; 0349 return nullptr; 0350 } 0351 0352 const QString srcPath = fileOrDirectory.toLocalFile(); 0353 0354 DVcsJob *job = new DVcsJob(workingDir, this, KDevelop::OutputJob::Silent); 0355 0356 *job << "hg" << "diff" << "-g"; 0357 0358 // Hg cannot do a diff from 0359 // SomeRevision to Previous, or 0360 // Working to SomeRevsion directly, but the reverse 0361 if (dstRev.isNull() // Destination is "Previous" 0362 || (!srcRev.isNull() && srcRev.isEmpty()) // Source is "Working" 0363 ) { 0364 std::swap(srcRev, dstRev); 0365 *job << "--reverse"; 0366 } 0367 0368 // unified diff 0369 *job << "-U" << "3"; // Default from GNU diff 0370 0371 if (srcRev.isNull() /* from "Previous" */ && dstRev.isEmpty() /* to "Working" */) { 0372 // Do nothing, that is the default 0373 } else if (srcRev.isNull()) { // Changes made in one arbitrary revision 0374 *job << "-c" << dstRev; 0375 } else { 0376 *job << "-r" << srcRev; 0377 if (!dstRev.isEmpty()) { 0378 *job << "-r" << dstRev; 0379 } 0380 } 0381 0382 *job << "--" << srcPath; 0383 0384 connect(job, &DVcsJob::readyForParsing, this, &MercurialPlugin::parseDiff); 0385 0386 return job; 0387 } 0388 0389 VcsJob *MercurialPlugin::remove(const QList<QUrl> &files) 0390 { 0391 if (files.empty()) { 0392 return nullptr; 0393 } 0394 0395 DVcsJob *job = new DVcsJob(findWorkingDir(files.first()), this); 0396 *job << "hg" << "rm" << "--" << files; 0397 return job; 0398 } 0399 0400 VcsJob *MercurialPlugin::status(const QList<QUrl> &localLocations, IBasicVersionControl::RecursionMode recursion) 0401 { 0402 QList<QUrl> locations = localLocations; 0403 if (recursion == NonRecursive) { 0404 filterOutDirectories(locations); 0405 } 0406 0407 if (locations.empty()) { 0408 return nullptr; 0409 } 0410 0411 DVcsJob *job = new DVcsJob(findWorkingDir(locations.first()), this, KDevelop::OutputJob::Silent); 0412 *job << "hg" << "status" << "-A" << "--" << locations; 0413 0414 connect(job, &DVcsJob::readyForParsing, this, &MercurialPlugin::parseStatus); 0415 0416 return job; 0417 } 0418 0419 bool MercurialPlugin::parseStatus(DVcsJob *job) const 0420 { 0421 if (job->status() != VcsJob::JobSucceeded) { 0422 qCDebug(PLUGIN_MERCURIAL) << "Job failed: " << job->output(); 0423 return false; 0424 } 0425 0426 const QString dir = job->directory().absolutePath().append(QDir::separator()); 0427 qCDebug(PLUGIN_MERCURIAL) << "Job succeeded for " << dir; 0428 const QStringList output = job->output().split('\n', QString::SkipEmptyParts); 0429 QList<QVariant> filestatus; 0430 0431 QSet<QUrl> conflictedFiles; 0432 0433 QStringList::const_iterator it = output.constBegin(); 0434 0435 // FIXME: Revive this functionality and add tests for it! 0436 // conflicts first 0437 // for (; it != output.constEnd(); it++) { 0438 // QChar stCh = it->at(0); 0439 // if (stCh == '%') { 0440 // it++; 0441 // break; 0442 // } 0443 // 0444 // QUrl file = QUrl::fromLocalFile(it->mid(2).prepend(dir)); 0445 // 0446 // VcsStatusInfo status; 0447 // status.setUrl(file); 0448 // // FIXME: conflicts resolved 0449 // status.setState(VcsStatusInfo::ItemHasConflicts); 0450 // 0451 // conflictedFiles.insert(file); 0452 // filestatus.append(qVariantFromValue(status)); 0453 // } 0454 0455 // standard statuses next 0456 for (; it != output.constEnd(); it++) { 0457 QChar stCh = it->at(0); 0458 0459 QUrl file = QUrl::fromLocalFile(it->mid(2).prepend(dir)); 0460 0461 if (!conflictedFiles.contains(file)) { 0462 VcsStatusInfo status; 0463 status.setUrl(file); 0464 status.setState(charToState(stCh.toLatin1())); 0465 filestatus.append(qVariantFromValue(status)); 0466 } 0467 } 0468 0469 job->setResults(qVariantFromValue(filestatus)); 0470 return true; 0471 } 0472 0473 VcsJob *MercurialPlugin::revert(const QList<QUrl> &localLocations, 0474 IBasicVersionControl::RecursionMode recursion) 0475 { 0476 QList<QUrl> locations = localLocations; 0477 if (recursion == NonRecursive) { 0478 filterOutDirectories(locations); 0479 } 0480 0481 if (locations.empty()) { 0482 return nullptr; 0483 } 0484 0485 DVcsJob *job = new DVcsJob(findWorkingDir(locations.first()), this); 0486 *job << "hg" << "revert" << "--" << locations; 0487 return job; 0488 } 0489 0490 VcsJob *MercurialPlugin::log(const QUrl &localLocation, 0491 const VcsRevision &rev, 0492 unsigned long limit) 0493 { 0494 return log(localLocation, VcsRevision::createSpecialRevision(VcsRevision::Start), rev, limit); 0495 } 0496 0497 VcsJob *MercurialPlugin::log(const QUrl &localLocation, 0498 const VcsRevision &rev, 0499 const VcsRevision &limit) 0500 { 0501 return log(localLocation, rev, limit, 0); 0502 } 0503 0504 0505 VcsJob *MercurialPlugin::log(const QUrl &localLocation, 0506 const VcsRevision &to, 0507 const VcsRevision &from, 0508 unsigned long limit) 0509 { 0510 DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this, KDevelop::OutputJob::Silent); 0511 0512 *job << "hg" << "log" << "-r" << toMercurialRevision(from) + ':' + toMercurialRevision(to); 0513 0514 if (limit > 0) 0515 *job << "-l" << QString::number(limit); 0516 0517 *job << "--template" 0518 << logTemplate() << "--" << localLocation; 0519 0520 connect(job, &DVcsJob::readyForParsing, this, 0521 &MercurialPlugin::parseLogOutputBasicVersionControl); 0522 return job; 0523 } 0524 0525 VcsJob *MercurialPlugin::annotate(const QUrl &localLocation, 0526 const VcsRevision &rev) 0527 { 0528 return new MercurialAnnotateJob(findWorkingDir(localLocation), rev, localLocation, this); 0529 } 0530 0531 class MergeBranchJob : public MercurialJob 0532 { 0533 public: 0534 MergeBranchJob(const QDir &workingDir, const QString& branchName, MercurialPlugin* parent) 0535 : MercurialJob(workingDir, parent, JobType::Merge), 0536 m_branchName(branchName) 0537 {} 0538 0539 void start() override 0540 { 0541 m_status = JobRunning; 0542 0543 auto job = new DVcsJob(m_workingDir, vcsPlugin(), KDevelop::OutputJob::Silent); 0544 *job << "hg" << "log" << "-b" << m_branchName << "-l" << "1" << "--template" << "{rev}"; 0545 0546 connect(job, &DVcsJob::resultsReady, this, [this](VcsJob* j) { 0547 auto job = static_cast<DVcsJob *>(j); 0548 if (job->status() != VcsJob::JobSucceeded || job->output().isEmpty()) 0549 return setFail(); 0550 0551 auto mergeJob = new DVcsJob(m_workingDir, vcsPlugin()); 0552 0553 *mergeJob << "hg" << "merge" << "-r" << job->output().trimmed(); 0554 0555 connect(mergeJob, &KJob::finished, this, [this](KJob *job) { 0556 if (job->error()) { 0557 setFail(); 0558 } 0559 }); 0560 connect(mergeJob, &DVcsJob::resultsReady, this, [this](VcsJob* j) { 0561 auto job = static_cast<DVcsJob *>(j); 0562 if (job->status() != VcsJob::JobSucceeded || job->output().isEmpty()) 0563 return setFail(); 0564 setSuccess(); 0565 }); 0566 0567 m_job = mergeJob; 0568 mergeJob->start(); 0569 }); 0570 connect(job, &KJob::finished, this, [this](KJob *job) { 0571 if (job->error()) { 0572 setFail(); 0573 } 0574 }); 0575 m_job = job; 0576 job->start(); 0577 } 0578 0579 private: 0580 QString m_branchName; 0581 }; 0582 0583 KDevelop::VcsJob* MercurialPlugin::mergeBranch(const QUrl& repository, const QString& branchName) 0584 { 0585 Q_ASSERT(!branchName.isEmpty()); 0586 0587 return new MergeBranchJob(findWorkingDir(repository), branchName, this); 0588 } 0589 0590 VcsJob *MercurialPlugin::heads(const QUrl &localLocation) 0591 { 0592 DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this, KDevelop::OutputJob::Silent); 0593 0594 *job << "hg" << "heads" << "--template" << logTemplate(); 0595 0596 connect(job, &DVcsJob::readyForParsing, this, 0597 &MercurialPlugin::parseLogOutputBasicVersionControl); 0598 return job; 0599 } 0600 0601 VcsJob *MercurialPlugin::identify(const QUrl &localLocation) 0602 { 0603 DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this, KDevelop::OutputJob::Silent); 0604 0605 *job << "hg" << "identify" << "-n"; 0606 0607 connect(job, &DVcsJob::readyForParsing, this, 0608 &MercurialPlugin::parseIdentify); 0609 return job; 0610 } 0611 0612 VcsJob *MercurialPlugin::checkoutHead(const QUrl &localLocation, const KDevelop::VcsRevision &rev) 0613 { 0614 DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); 0615 0616 *job << "hg" << "update" << "-C" << toMercurialRevision(rev); 0617 0618 return job; 0619 } 0620 0621 VcsJob *MercurialPlugin::mergeWith(const QUrl &localLocation, const KDevelop::VcsRevision &rev) 0622 { 0623 DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); 0624 0625 *job << "hg" << "merge" << "-r" << toMercurialRevision(rev); 0626 0627 return job; 0628 } 0629 0630 VcsJob *MercurialPlugin::branch(const QUrl &repository, const VcsRevision &/*rev*/, const QString &branchName) 0631 { 0632 DVcsJob *job = new DVcsJob(findWorkingDir(repository), this, OutputJob::Silent); 0633 *job << "hg" << "branch" << "--" << branchName; 0634 return job; 0635 } 0636 0637 VcsJob *MercurialPlugin::branches(const QUrl &repository) 0638 { 0639 DVcsJob *job = new DVcsJob(findWorkingDir(repository), this, OutputJob::Silent); 0640 *job << "hg" << "branches"; 0641 connect(job, &DVcsJob::readyForParsing, this, &MercurialPlugin::parseMultiLineOutput); 0642 return job; 0643 } 0644 0645 VcsJob *MercurialPlugin::currentBranch(const QUrl &repository) 0646 { 0647 DVcsJob *job = new DVcsJob(findWorkingDir(repository), this, OutputJob::Silent); 0648 *job << "hg" << "branch"; 0649 connect(job, &DVcsJob::readyForParsing, this, &MercurialPlugin::parseMultiLineOutput); 0650 return job; 0651 } 0652 0653 VcsJob *MercurialPlugin::deleteBranch(const QUrl &/*repository*/, const QString &/*branchName*/) 0654 { 0655 // Not supported 0656 return nullptr; 0657 } 0658 0659 VcsJob *MercurialPlugin::renameBranch(const QUrl &/*repository*/, const QString &/*oldBranchName*/, const QString &/*newBranchName*/) 0660 { 0661 // Not supported 0662 return nullptr; 0663 } 0664 0665 VcsJob *MercurialPlugin::switchBranch(const QUrl &repository, const QString &branchName) 0666 { 0667 DVcsJob *job = new DVcsJob(findWorkingDir(repository), this); 0668 *job << "hg" << "update" << "--" << branchName; 0669 return job; 0670 } 0671 0672 VcsJob *MercurialPlugin::tag(const QUrl &repository, const QString &commitMessage, const VcsRevision &rev, const QString &tagName) 0673 { 0674 DVcsJob *job = new DVcsJob(findWorkingDir(repository), this); 0675 *job << "hg" << "tag" << "-m" << commitMessage << "-r" << toMercurialRevision(rev) << "--" << tagName; 0676 return job; 0677 } 0678 0679 QVector<DVcsEvent> MercurialPlugin::allCommits(const QString& repo) 0680 { 0681 DVcsJob *job = new DVcsJob(findWorkingDir(QUrl::fromLocalFile(repo)), this, OutputJob::Silent); 0682 0683 *job << "hg" << "log" << "--template" << "{desc}\\_%{date|isodate}\\_%{author}\\_%{parents}\\_%{node}\\_%{rev}\\_%"; 0684 0685 if (!job->exec() || job->status() != VcsJob::JobSucceeded) 0686 return QVector<DVcsEvent>(); 0687 0688 QVector<DVcsEvent> commits; 0689 0690 parseLogOutput(job, commits); 0691 return commits; 0692 } 0693 0694 void MercurialPlugin::parseMultiLineOutput(DVcsJob *job) const 0695 { 0696 qCDebug(PLUGIN_MERCURIAL) << job->output(); 0697 job->setResults(job->output().split('\n', QString::SkipEmptyParts)); 0698 } 0699 0700 void MercurialPlugin::parseDiff(DVcsJob *job) 0701 { 0702 if (job->status() != VcsJob::JobSucceeded) { 0703 qCDebug(PLUGIN_MERCURIAL) << "Parse-job failed: " << job->output(); 0704 return; 0705 } 0706 0707 // Diffs are generated relativly to the repository root, 0708 // so we have recover it from the job. 0709 // Not quite clean m_lastRepoRoot holds the root, after querying isValidDirectory() 0710 QString workingDir(job->directory().absolutePath()); 0711 isValidDirectory(QUrl::fromLocalFile(workingDir)); 0712 QString repoRoot = m_lastRepoRoot.adjusted(QUrl::StripTrailingSlash).path(); 0713 0714 VcsDiff diff; 0715 0716 // We have to recover the type of the diff somehow. 0717 QString output = job->output(); 0718 0719 // hg diff adds a prefix to the paths, which we will now strip 0720 QRegExp reg("(diff [^\n]+\n--- )a(/[^\n]+\n\\+\\+\\+ )b(/[^\n]+\n)"); 0721 QString replacement("\\1" + repoRoot + "\\2" + repoRoot + "\\3"); 0722 output.replace(reg, replacement); 0723 0724 diff.setDiff(output); 0725 diff.setBaseDiff(QUrl::fromLocalFile(repoRoot)); 0726 diff.setDepth(0); 0727 0728 job->setResults(qVariantFromValue(diff)); 0729 } 0730 0731 void MercurialPlugin::parseLogOutputBasicVersionControl(DVcsJob *job) const 0732 { 0733 QList<QVariant> events; 0734 static unsigned int entriesPerCommit = 10; 0735 auto items = job->output().split("\\_%"); 0736 0737 qCDebug(PLUGIN_MERCURIAL) << items; 0738 /* remove trailing \\_% */ 0739 items.removeLast(); 0740 0741 if ((items.count() % entriesPerCommit) != 0) { 0742 qCDebug(PLUGIN_MERCURIAL) << "Cannot parse commit log: unexpected number of entries"; 0743 return; 0744 } 0745 0746 /* get revision data from items. 0747 * because there is continuous stream of \\_% separated strings, 0748 * incrementation will occur inside loop body 0749 * 0750 * "{desc}\\_%{date|rfc3339date}\\_%{author}\\_%{parents}\\_%{node}\\_%{rev}\\_%" 0751 * "{file_dels}\\_%{file_adds}\\_%{file_mods}\\_%{file_copies}\\_%' 0752 */ 0753 for (auto it = items.constBegin(); it != items.constEnd();) { 0754 QString desc = *it++; 0755 qCDebug(PLUGIN_MERCURIAL) << desc; 0756 Q_ASSERT(!desc.isEmpty()); 0757 QString date = *it++; 0758 qCDebug(PLUGIN_MERCURIAL) << date; 0759 Q_ASSERT(!date.isEmpty()); 0760 QString author = *it++; 0761 qCDebug(PLUGIN_MERCURIAL) << author; 0762 Q_ASSERT(!author.isEmpty()); 0763 QString parents = *it++; 0764 QString node = *it++; 0765 QString rev = *it++; 0766 qCDebug(PLUGIN_MERCURIAL) << rev; 0767 Q_ASSERT(!rev.isEmpty()); 0768 0769 VcsEvent event; 0770 event.setMessage(desc); 0771 event.setDate(QDateTime::fromString(date, Qt::ISODate)); 0772 event.setAuthor(author); 0773 0774 VcsRevision revision; 0775 revision.setRevisionValue(rev.toLongLong(), KDevelop::VcsRevision::GlobalNumber); 0776 event.setRevision(revision); 0777 0778 QList<VcsItemEvent> items; 0779 0780 const VcsItemEvent::Action actions[3] = {VcsItemEvent::Deleted, VcsItemEvent::Added, VcsItemEvent::ContentsModified}; 0781 for (int i = 0; i < 3; i++) { 0782 const auto &files = *it++; 0783 if (files.isEmpty()) { 0784 continue; 0785 } 0786 0787 foreach (const auto& file, files.split(' ')) { 0788 VcsItemEvent item; 0789 item.setActions(actions[i]); 0790 item.setRepositoryLocation(QUrl::fromPercentEncoding(file.toLocal8Bit())); 0791 items.push_back(item); 0792 } 0793 } 0794 0795 const auto &copies = *it++; 0796 if (!copies.isEmpty()) { 0797 foreach (const auto & copy, copies.split(' ')) { 0798 auto files = copy.split('~'); 0799 0800 // TODO: What is it? Why it doesn't work 0801 if (files.size() >= 2) { 0802 VcsItemEvent item; 0803 item.setActions(VcsItemEvent::Copied); 0804 item.setRepositoryCopySourceLocation(QUrl::fromPercentEncoding(files[0].toLocal8Bit())); 0805 item.setRepositoryLocation(QUrl::fromPercentEncoding(files[1].toLocal8Bit())); 0806 items.push_back(item); 0807 } 0808 } 0809 } 0810 0811 event.setItems(items); 0812 events.push_back(QVariant::fromValue(event)); 0813 } 0814 0815 job->setResults(QVariant(events)); 0816 } 0817 0818 void MercurialPlugin::parseIdentify(DVcsJob *job) const 0819 { 0820 QString value = job->output(); 0821 QList<QVariant> result; 0822 0823 qCDebug(PLUGIN_MERCURIAL) << value; 0824 // remove last '\n' 0825 value.chop(1); 0826 0827 // remove last '+' if necessary 0828 if (value.endsWith('+')) 0829 value.chop(1); 0830 0831 foreach (const QString & rev, value.split('+')) { 0832 VcsRevision revision; 0833 revision.setRevisionValue(rev.toLongLong(), VcsRevision::GlobalNumber); 0834 result << qVariantFromValue<VcsRevision>(revision); 0835 } 0836 job->setResults(QVariant(result)); 0837 } 0838 0839 0840 void MercurialPlugin::parseLogOutput(const DVcsJob* job, QVector<DVcsEvent>& commits) const 0841 { 0842 qCDebug(PLUGIN_MERCURIAL) << "parseLogOutput"; 0843 0844 static unsigned int entriesPerCommit = 6; 0845 auto items = job->output().split("\\_%"); 0846 0847 if (uint(items.size()) < entriesPerCommit || 1 != (items.size() % entriesPerCommit)) { 0848 qCDebug(PLUGIN_MERCURIAL) << "Cannot parse commit log: unexpected number of entries"; 0849 return; 0850 } 0851 0852 bool success = false; 0853 0854 QString const &lastRev = items.at(entriesPerCommit - 1); 0855 unsigned int id = lastRev.toUInt(&success); 0856 0857 if (!success) { 0858 qCDebug(PLUGIN_MERCURIAL) << "Could not parse last revision \"" << lastRev << '"'; 0859 id = 1024; 0860 } 0861 0862 QVector<QString> fullIds(id + 1); 0863 0864 typedef std::reverse_iterator<QStringList::const_iterator> QStringListReverseIterator; 0865 QStringListReverseIterator rbegin(items.constEnd() - 1), rend(items.constBegin()); // Skip the final 0 0866 unsigned int lastId; 0867 0868 while (rbegin != rend) { 0869 QString rev = *rbegin++; 0870 QString node = *rbegin++; 0871 QString parents = *rbegin++; 0872 QString author = *rbegin++; 0873 QString date = *rbegin++; 0874 QString desc = *rbegin++; 0875 lastId = id; 0876 id = rev.toUInt(&success); 0877 0878 if (!success) { 0879 qCDebug(PLUGIN_MERCURIAL) << "Could not parse revision \"" << rev << '"'; 0880 return; 0881 } 0882 0883 if (uint(fullIds.size()) <= id) { 0884 fullIds.resize(id * 2); 0885 } 0886 0887 fullIds[id] = node; 0888 0889 DVcsEvent commit; 0890 commit.setCommit(node); 0891 commit.setAuthor(author); 0892 commit.setDate(date); 0893 commit.setLog(desc); 0894 0895 if (id == 0) { 0896 commit.setType(DVcsEvent::INITIAL); 0897 } else { 0898 if (parents.isEmpty() && id != 0) { 0899 commit.setParents(QStringList(fullIds[lastId])); 0900 } else { 0901 QStringList parentList; 0902 QStringList unparsedParentList = parents.split(QChar(' '), QString::SkipEmptyParts); 0903 // id:Short-node 0904 static const unsigned int shortNodeSuffixLen = 13; 0905 foreach (const QString & p, unparsedParentList) { 0906 QString ids = p.left(p.size() - shortNodeSuffixLen); 0907 id = ids.toUInt(&success); 0908 0909 if (!success) { 0910 qCDebug(PLUGIN_MERCURIAL) << "Could not parse parent-revision \"" << ids << "\" of revision " << rev; 0911 return; 0912 } 0913 0914 parentList.push_back(fullIds[id]); 0915 } 0916 0917 commit.setParents(parentList); 0918 } 0919 } 0920 0921 commits.push_front(commit); 0922 } 0923 } 0924 0925 VcsLocationWidget *MercurialPlugin::vcsLocation(QWidget *parent) const 0926 { 0927 return new MercurialVcsLocationWidget(parent); 0928 } 0929 0930 void MercurialPlugin::filterOutDirectories(QList<QUrl> &locations) 0931 { 0932 QList<QUrl> fileLocations; 0933 foreach (const QUrl & location, locations) { 0934 if (!QFileInfo(location.toLocalFile()).isDir()) { 0935 fileLocations << location; 0936 } 0937 } 0938 locations = fileLocations; 0939 } 0940 0941 VcsStatusInfo::State MercurialPlugin::charToState(const char ch) 0942 { 0943 switch (ch) { 0944 case 'M': 0945 return VcsStatusInfo::ItemModified; 0946 case 'A': 0947 return VcsStatusInfo::ItemAdded; 0948 case 'R': 0949 return VcsStatusInfo::ItemDeleted; 0950 case 'C': 0951 return VcsStatusInfo::ItemUpToDate; 0952 case '!': // Missing 0953 return VcsStatusInfo::ItemUserState; 0954 default: 0955 return VcsStatusInfo::ItemUnknown; 0956 } 0957 } 0958 0959 QString MercurialPlugin::toMercurialRevision(const VcsRevision &vcsrev) 0960 { 0961 switch (vcsrev.revisionType()) { 0962 case VcsRevision::Special: 0963 switch (vcsrev.revisionValue().value<KDevelop::VcsRevision::RevisionSpecialType>()) { 0964 case VcsRevision::Head: 0965 return QString("tip"); 0966 case VcsRevision::Base: 0967 return QString("."); 0968 case VcsRevision::Working: 0969 return QString(""); 0970 case VcsRevision::Previous: // We can't determine this from one revision alone 0971 return QString(); 0972 case VcsRevision::Start: 0973 return QString("0"); 0974 default: 0975 return QString(); 0976 } 0977 case VcsRevision::GlobalNumber: 0978 return QString::number(vcsrev.revisionValue().toLongLong()); 0979 case VcsRevision::Date: // TODO 0980 case VcsRevision::FileNumber: // No file number for mercurial 0981 default: 0982 return QString(); 0983 } 0984 } 0985 0986 QDir MercurialPlugin::findWorkingDir(const QUrl &location) 0987 { 0988 QFileInfo fileInfo(location.toLocalFile()); 0989 0990 // find out correct working directory 0991 if (fileInfo.isFile()) { 0992 return fileInfo.absolutePath(); 0993 } else { 0994 return fileInfo.absoluteFilePath(); 0995 } 0996 } 0997 0998 QUrl MercurialPlugin::remotePushRepositoryLocation(QDir &directory) 0999 { 1000 // check default-push first 1001 DVcsJob *job = new DVcsJob(directory, this); 1002 *job << "hg" << "paths" << "default-push"; 1003 if (!job->exec() || job->status() != VcsJob::JobSucceeded) { 1004 qCDebug(PLUGIN_MERCURIAL) << "no default-push, hold on"; 1005 1006 // or try default 1007 job = new DVcsJob(directory, this); 1008 *job << "hg" << "paths" << "default"; 1009 1010 if (!job->exec() || job->status() != VcsJob::JobSucceeded) { 1011 qCDebug(PLUGIN_MERCURIAL) << "nowhere to push!"; 1012 1013 // fail is everywhere 1014 return QUrl(); 1015 } 1016 } 1017 1018 // don't forget to strip last '\n' 1019 return QUrl::fromLocalFile(job->output().trimmed()); 1020 } 1021 1022 void MercurialPlugin::registerRepositoryForCurrentBranchChanges(const QUrl& repository) 1023 { 1024 if (isValidDirectory(repository)) { 1025 QString headFile = m_lastRepoRoot.path() + QStringLiteral("/.hg/branch"); 1026 m_watcher->addFile(headFile); 1027 } 1028 } 1029 1030 void MercurialPlugin::additionalMenuEntries(QMenu *menu, const QList<QUrl> &urls) 1031 { 1032 m_urls = urls; 1033 1034 menu->addAction(m_headsAction); 1035 menu->addSeparator()->setText(i18n("Mercurial Queues")); 1036 menu->addAction(m_mqNew); 1037 menu->addAction(m_mqPushAction); 1038 menu->addAction(m_mqPushAllAction); 1039 menu->addAction(m_mqPopAction); 1040 menu->addAction(m_mqPopAllAction); 1041 menu->addAction(m_mqManagerAction); 1042 1043 m_headsAction->setEnabled(m_urls.count() == 1); 1044 1045 //FIXME:not supported yet, so disable 1046 m_mqNew->setEnabled(false); 1047 m_mqPushAction->setEnabled(false); 1048 m_mqPushAllAction->setEnabled(false); 1049 m_mqPopAction->setEnabled(false); 1050 m_mqPopAllAction->setEnabled(false); 1051 } 1052 1053 void MercurialPlugin::showHeads() 1054 { 1055 const QUrl ¤t = m_urls.first(); 1056 MercurialHeadsWidget *headsWidget = new MercurialHeadsWidget(this, current); 1057 headsWidget->show(); 1058 } 1059 1060 void MercurialPlugin::showMercurialQueuesManager() 1061 { 1062 const QUrl ¤t = m_urls.first(); 1063 MercurialQueuesManager *managerWidget = new MercurialQueuesManager(this, current); 1064 managerWidget->show(); 1065 } 1066 1067 /* 1068 * Mercurial Queues 1069 */ 1070 1071 VcsJob *MercurialPlugin::mqNew(const QUrl &/*localLocation*/, const QString &/*name*/, const QString &/*message*/) 1072 { 1073 return nullptr; 1074 } 1075 1076 VcsJob *MercurialPlugin::mqPush(const QUrl &localLocation) 1077 { 1078 DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); 1079 *job << "hg" << "qpush"; 1080 return job; 1081 } 1082 1083 VcsJob *MercurialPlugin::mqPushAll(const QUrl &localLocation) 1084 { 1085 DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); 1086 *job << "hg" << "qpush" << "-a"; 1087 return job; 1088 } 1089 1090 VcsJob *MercurialPlugin::mqPop(const QUrl &localLocation) 1091 { 1092 DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); 1093 *job << "hg" << "qpop"; 1094 return job; 1095 } 1096 1097 VcsJob *MercurialPlugin::mqPopAll(const QUrl &localLocation) 1098 { 1099 DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); 1100 *job << "hg" << "qpop" << "-a"; 1101 return job; 1102 } 1103 1104 VcsJob *MercurialPlugin::mqApplied(const QUrl &localLocation) 1105 { 1106 DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); 1107 *job << "hg" << "qapplied"; 1108 connect(job, &DVcsJob::readyForParsing, this, &MercurialPlugin::parseMultiLineOutput); 1109 return job; 1110 } 1111 1112 VcsJob *MercurialPlugin::mqUnapplied(const QUrl &localLocation) 1113 { 1114 DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); 1115 *job << "hg" << "qunapplied"; 1116 connect(job, &DVcsJob::readyForParsing, this, &MercurialPlugin::parseMultiLineOutput); 1117 return job; 1118 } 1119 1120 // #include "mercurialplugin.moc"