diff --git a/mercurialplugin.cpp b/mercurialplugin.cpp index d04c11e..ab9a057 100644 --- a/mercurialplugin.cpp +++ b/mercurialplugin.cpp @@ -1,1056 +1,1055 @@ /*************************************************************************** * 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 "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); } MercurialPlugin::~MercurialPlugin() { } QString MercurialPlugin::name() const { return QLatin1String("Mercurial"); } 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 // TODO: Check whether this is the right port, original code was: m_lastRepoRoot.setDirectory(dir.absolutePath()); m_lastRepoRoot.setPath(dir.absolutePath()); return true; } 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) { /* TODO: update to Head != pull, but for consistency with git plugin... * TODO: per file pulls? * TODO: why rev == VcsRevision::createSpecialRevision(VcsRevision::Head) doesn't work? */ if (rev.revisionType() == VcsRevision::Special && rev.revisionValue().value() == VcsRevision::Head) { return pull(VcsLocation(), QUrl::fromLocalFile(findWorkingDir(localLocations.first()).path())); } /* * actually reverting files * TODO: hg revert does not change parents, hg update does * hg revert works on files, hg update doesn't * maybe do hg update only when top dir update() is requested? * TODO: nobody calls update with something other than VcsRevision::Head */ QList locations = localLocations; if (recursion == NonRecursive) { filterOutDirectories(locations); } if (locations.empty()) { return nullptr; } DVcsJob *job = new DVcsJob(findWorkingDir(locations.first()), this); //Note: the message is quoted somewhere else, so if we quote here then we have quotes in the commit log // idk what does previous comment mean -- abatyiev *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); } KDevelop::VcsJob* MercurialPlugin::mergeBranch(const QUrl &/*repository*/, const QString &/*branchName*/) { // TODO: return nullptr; } VcsJob *MercurialPlugin::heads(const QUrl &localLocation) { - DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this); + DVcsJob *job = new DVcsJob(findWorkingDir(localLocation), this, KDevelop::OutputJob::Silent); - *job << "hg" << "heads" << "--template" - << logTemplate() << "--" << localLocation; + *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*/) { return nullptr; } VcsJob *MercurialPlugin::renameBranch(const QUrl &/*repository*/, const QString &/*oldBranchName*/, const QString &/*newBranchName*/) { 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); } } // TODO: Actually draw a graph, i.e use commit.setProperties as needed 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*/) { // TODO } 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/models/mercurialheadsmodel.cpp b/models/mercurialheadsmodel.cpp index 7ace69c..ea01a43 100644 --- a/models/mercurialheadsmodel.cpp +++ b/models/mercurialheadsmodel.cpp @@ -1,48 +1,99 @@ -/*************************************************************************** - * 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 . * - ***************************************************************************/ +/* + This file is part of KDevelop + + Copyright 2011 Andrey Batyiev + 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 "mercurialheadsmodel.h" +#include "../mercurialplugin.h" + #include +#include +#include + +#include #include #include using namespace KDevelop; -MercurialHeadsModel::MercurialHeadsModel(IBasicVersionControl *iface, const KDevelop::VcsRevision &rev, const QUrl &url, QObject *parent) - : VcsEventModel(iface, rev, url, parent) +MercurialHeadsModel::MercurialHeadsModel(MercurialPlugin* plugin, const QUrl& url, QObject* parent) + : VcsBasicEventModel(parent) + , m_updating(false) + , m_plugin(plugin) + , m_url(url) { - } QVariant MercurialHeadsModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Vertical && role == Qt::DecorationRole) { const VcsRevision &rev = eventForIndex(index(section, 0)).revision(); if (m_currentHeads.contains(rev)) { return QVariant(QIcon("arrow-right")); } else { return QVariant(); } } else { - return VcsEventModel::headerData(section, orientation, role); + return VcsBasicEventModel::headerData(section, orientation, role); + } +} + +void MercurialHeadsModel::update() +{ + VcsJob *identifyJob = m_plugin->identify(m_url); + connect(identifyJob, &VcsJob::resultsReady, this, &MercurialHeadsModel::identifyReceived); + ICore::self()->runController()->registerJob(identifyJob); + + VcsJob *headsJob = m_plugin->heads(m_url); + connect(headsJob, &VcsJob::resultsReady, this, &MercurialHeadsModel::headsReceived); + ICore::self()->runController()->registerJob(headsJob); +} + +void MercurialHeadsModel::identifyReceived(VcsJob *job) +{ + QList currentHeads; + foreach (const QVariant& value, job->fetchResults().toList()) { + currentHeads << value.value(); + } + m_currentHeads = currentHeads; + if (m_updating) { + emit updateComplete(); + m_updating = false; + } else { + m_updating = true; + } +} + +void MercurialHeadsModel::headsReceived(VcsJob *job) +{ + QList events; + foreach (const QVariant& value, job->fetchResults().toList()) { + events << value.value(); + } + addEvents(events); + if (m_updating) { + emit updateComplete(); + m_updating = false; + } else { + m_updating = true; } } diff --git a/models/mercurialheadsmodel.h b/models/mercurialheadsmodel.h index 1ef91b9..49deb47 100644 --- a/models/mercurialheadsmodel.h +++ b/models/mercurialheadsmodel.h @@ -1,41 +1,60 @@ -/*************************************************************************** - * 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 . * - ***************************************************************************/ +/* + This file is part of KDevelop + + Copyright 2011 Andrey Batyiev + 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. +*/ #ifndef MERCURIALHEADSMODEL_H #define MERCURIALHEADSMODEL_H #include "vcs/models/vcseventmodel.h" -class MercurialHeadsModel : public KDevelop::VcsEventModel +#include + +namespace KDevelop +{ + class VcsJob; +} + +class MercurialPlugin; + +class MercurialHeadsModel : public KDevelop::VcsBasicEventModel { Q_OBJECT public: - MercurialHeadsModel(KDevelop::IBasicVersionControl *iface, const KDevelop::VcsRevision &rev, const QUrl &url, QObject *parent); + MercurialHeadsModel(MercurialPlugin* plugin, const QUrl& url, QObject* parent); QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - void setCurrentHeads(const QList ¤tHeads) { - m_currentHeads = currentHeads; - emit headerDataChanged(Qt::Vertical, 0, m_currentHeads.count()); - } + + void update(); + +signals: + void updateComplete(); + +private slots: + void headsReceived(KDevelop::VcsJob* job); + void identifyReceived(KDevelop::VcsJob* job); private: + bool m_updating; + MercurialPlugin* m_plugin; + QUrl m_url; QList m_currentHeads; }; #endif // MERCURIALHEADSMODEL_H diff --git a/tests/test_mercurial.cpp b/tests/test_mercurial.cpp index fce838d..dfca1a1 100644 --- a/tests/test_mercurial.cpp +++ b/tests/test_mercurial.cpp @@ -1,364 +1,396 @@ /* This file is part of KDevelop Copyright 2008 Evgeniy Ivanov Copyright 2009 Fabian Wiesel 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 "test_mercurial.h" #include #include #include +#include #include #include #include #include #include "../mercurialplugin.h" #include "../mercurialannotatejob.h" +#include "../models/mercurialheadsmodel.h" #include "debug.h" using namespace KDevelop; namespace { const QString tempDir = QDir::tempPath(); const QString MercurialTestDir1("kdevMercurial_testdir"); const QString mercurialTest_BaseDir(tempDir + "/kdevMercurial_testdir/"); const QString mercurialTest_BaseDir2(tempDir + "/kdevMercurial_testdir2/"); const QString mercurialRepo(mercurialTest_BaseDir + ".hg"); const QString mercurialSrcDir(mercurialTest_BaseDir + "src/"); const QString mercurialTest_FileName("testfile"); const QString mercurialTest_FileName2("foo"); const QString mercurialTest_FileName3("bar"); void verifyJobSucceed(VcsJob* job) { QVERIFY(job); QVERIFY(job->exec()); QVERIFY(job->status() == KDevelop::VcsJob::JobSucceeded); } void writeToFile(const QString& path, const QString& content, QIODevice::OpenModeFlag mode = QIODevice::WriteOnly) { QFile f(path); QVERIFY(f.open(mode)); QTextStream input(&f); input << content << endl; } } void MercurialTest::initTestCase() { AutoTestShell::init({"kdevmercurial"}); m_testCore = new KDevelop::TestCore(); m_testCore->initialize(KDevelop::Core::NoUi); m_proxy = new MercurialPlugin(m_testCore); } void MercurialTest::cleanupTestCase() { delete m_proxy; removeTempDirs(); } void MercurialTest::init() { QDir tmpdir(tempDir); tmpdir.mkdir(mercurialTest_BaseDir); tmpdir.mkdir(mercurialSrcDir); tmpdir.mkdir(mercurialTest_BaseDir2); } void MercurialTest::cleanup() { removeTempDirs(); } void MercurialTest::repoInit() { mercurialDebug() << "Trying to init repo"; // create the local repository VcsJob *j = m_proxy->init(QUrl::fromLocalFile(mercurialTest_BaseDir)); verifyJobSucceed(j); QVERIFY(QFileInfo(mercurialRepo).exists()); QVERIFY(m_proxy->isValidDirectory(QUrl::fromLocalFile(mercurialTest_BaseDir))); QVERIFY(!m_proxy->isValidDirectory(QUrl::fromLocalFile("/tmp"))); } void MercurialTest::addFiles() { mercurialDebug() << "Adding files to the repo"; writeToFile(mercurialTest_BaseDir + mercurialTest_FileName, "commit 0 content"); writeToFile(mercurialTest_BaseDir + mercurialTest_FileName2, "commit 0 content, foo"); VcsJob *j = m_proxy->status({QUrl::fromLocalFile(mercurialTest_BaseDir)}, KDevelop::IBasicVersionControl::Recursive); verifyJobSucceed(j); auto statusResults = j->fetchResults().toList(); QCOMPARE(statusResults.size(), 2); auto status = statusResults[0].value(); QCOMPARE(status.url(), QUrl::fromLocalFile(mercurialTest_BaseDir + mercurialTest_FileName2)); QCOMPARE(status.state(), VcsStatusInfo::ItemUnknown); status = statusResults[1].value(); QCOMPARE(status.url(), QUrl::fromLocalFile(mercurialTest_BaseDir + mercurialTest_FileName)); QCOMPARE(status.state(), VcsStatusInfo::ItemUnknown); j = m_proxy->add({QUrl::fromLocalFile(mercurialTest_BaseDir)}, KDevelop::IBasicVersionControl::NonRecursive); QVERIFY(!j); // /tmp/kdevMercurial_testdir/ and kdevMercurial_testdir //add always should use aboslute path to the any directory of the repository, let's check: j = m_proxy->add({QUrl::fromLocalFile(mercurialTest_BaseDir), QUrl::fromLocalFile(MercurialTestDir1)}, KDevelop::IBasicVersionControl::Recursive); verifyJobSucceed(j); // /tmp/kdevMercurial_testdir/ and testfile j = m_proxy->add({QUrl::fromLocalFile(mercurialTest_BaseDir + mercurialTest_FileName)}, KDevelop::IBasicVersionControl::Recursive); verifyJobSucceed(j); writeToFile(mercurialSrcDir + mercurialTest_FileName3, "commit 0 content, bar"); j = m_proxy->status({QUrl::fromLocalFile(mercurialTest_BaseDir)}, KDevelop::IBasicVersionControl::Recursive); verifyJobSucceed(j); statusResults = j->fetchResults().toList(); QCOMPARE(statusResults.size(), 3); status = statusResults[0].value(); QCOMPARE(status.url(), QUrl::fromLocalFile(mercurialTest_BaseDir + mercurialTest_FileName2)); QCOMPARE(status.state(), VcsStatusInfo::ItemAdded); status = statusResults[1].value(); QCOMPARE(status.url(), QUrl::fromLocalFile(mercurialTest_BaseDir + mercurialTest_FileName)); QCOMPARE(status.state(), VcsStatusInfo::ItemAdded); status = statusResults[2].value(); QCOMPARE(status.url(), QUrl::fromLocalFile(mercurialSrcDir + mercurialTest_FileName3)); QCOMPARE(status.state(), VcsStatusInfo::ItemUnknown); // repository path without trailing slash and a file in a parent directory // /tmp/repo and /tmp/repo/src/bar j = m_proxy->add({QUrl::fromLocalFile(mercurialSrcDir + mercurialTest_FileName3)}); verifyJobSucceed(j); // let's use absolute path, because it's used in ContextMenus j = m_proxy->add({QUrl::fromLocalFile(mercurialTest_BaseDir + mercurialTest_FileName2)}, KDevelop::IBasicVersionControl::Recursive); verifyJobSucceed(j); //Now let's create several files and try "hg add file1 file2 file3" writeToFile(mercurialTest_BaseDir + "file1", "file1"); writeToFile(mercurialTest_BaseDir + "file2", "file2"); j = m_proxy->add({QUrl::fromLocalFile(mercurialTest_BaseDir + "file1"), QUrl::fromLocalFile(mercurialTest_BaseDir + "file2")}, KDevelop::IBasicVersionControl::Recursive); verifyJobSucceed(j); } void MercurialTest::commitFiles() { mercurialDebug() << "Committing..."; VcsJob *j = m_proxy->commit(QString("commit 0"), {QUrl::fromLocalFile(mercurialTest_BaseDir)}, KDevelop::IBasicVersionControl::Recursive); verifyJobSucceed(j); j = m_proxy->status({QUrl::fromLocalFile(mercurialTest_BaseDir)}, KDevelop::IBasicVersionControl::Recursive); verifyJobSucceed(j); // Test the results of the "mercurial add" DVcsJob *jobLs = new DVcsJob(mercurialTest_BaseDir, nullptr); *jobLs << "hg" << "stat" << "-q" << "-c" << "-n"; verifyJobSucceed(jobLs); QStringList files = jobLs->output().split("\n"); QVERIFY(files.contains(mercurialTest_FileName)); QVERIFY(files.contains(mercurialTest_FileName2)); QVERIFY(files.contains("src/" + mercurialTest_FileName3)); mercurialDebug() << "Committing one more time"; // let's try to change the file and test "hg commit -a" writeToFile(mercurialTest_BaseDir + mercurialTest_FileName, "commit 1 content", QIODevice::Append); j = m_proxy->add({QUrl::fromLocalFile(mercurialTest_BaseDir + mercurialTest_FileName)}, KDevelop::IBasicVersionControl::Recursive); verifyJobSucceed(j); j = m_proxy->commit(QString("commit 1"), {QUrl::fromLocalFile(mercurialTest_BaseDir)}, KDevelop::IBasicVersionControl::Recursive); verifyJobSucceed(j); } void MercurialTest::cloneRepository() { // make job that clones the local repository, created in the previous test VcsJob *j = m_proxy->createWorkingCopy(VcsLocation(mercurialTest_BaseDir), QUrl::fromLocalFile(mercurialTest_BaseDir2)); verifyJobSucceed(j); QVERIFY(QFileInfo(QString(mercurialTest_BaseDir2 + "/.hg/")).exists()); } void MercurialTest::testInit() { repoInit(); } void MercurialTest::testAdd() { repoInit(); addFiles(); } void MercurialTest::testCommit() { repoInit(); addFiles(); commitFiles(); } void MercurialTest::testBranching() { // TODO: } void MercurialTest::testRevisionHistory() { repoInit(); addFiles(); commitFiles(); QList commits = m_proxy->getAllCommits(mercurialTest_BaseDir); QCOMPARE(commits.count(), 2); QCOMPARE(commits[0].getParents().size(), 1); //initial commit is on the top QVERIFY(commits[1].getParents().isEmpty()); //0 is later than 1! QCOMPARE(commits[0].getLog(), QString("commit 1")); //0 is later than 1! QCOMPARE(commits[1].getLog(), QString("commit 0")); QVERIFY(commits[1].getCommit().contains(QRegExp("^\\w{,40}$"))); QVERIFY(commits[0].getCommit().contains(QRegExp("^\\w{,40}$"))); QVERIFY(commits[0].getParents()[0].contains(QRegExp("^\\w{,40}$"))); } void MercurialTest::removeTempDirs() { if (QFileInfo(mercurialTest_BaseDir).exists()) if (!(KIO::del(QUrl::fromLocalFile(mercurialTest_BaseDir))->exec())) mercurialDebug() << "KIO::del(" << mercurialTest_BaseDir << ") returned false"; if (QFileInfo(mercurialTest_BaseDir2).exists()) if (!(KIO::del(QUrl::fromLocalFile(mercurialTest_BaseDir2))->exec())) mercurialDebug() << "KIO::del(" << mercurialTest_BaseDir2 << ") returned false"; } void MercurialTest::testAnnotate() { repoInit(); addFiles(); commitFiles(); // TODO: Check annotation with a lot of commits (> 200) VcsRevision revision; revision.setRevisionValue(0, KDevelop::VcsRevision::GlobalNumber); auto job = m_proxy->annotate(QUrl::fromLocalFile(mercurialTest_BaseDir + mercurialTest_FileName), revision); verifyJobSucceed(job); auto results = job->fetchResults().toList(); QCOMPARE(results.size(), 2); auto commit0 = results[0].value(); QCOMPARE(commit0.text(), QStringLiteral("commit 0 content")); QCOMPARE(commit0.lineNumber(), 0); QVERIFY(commit0.date().isValid()); QCOMPARE(commit0.revision().revisionValue().toLongLong(), 0); auto commit1 = results[1].value(); QCOMPARE(commit1.text(), QStringLiteral("commit 1 content")); QCOMPARE(commit1.lineNumber(), 1); QVERIFY(commit1.date().isValid()); QCOMPARE(commit1.revision().revisionValue().toLongLong(), 1); // Let's change a file without commiting it writeToFile(mercurialTest_BaseDir + mercurialTest_FileName, "commit 2 content (temporary)", QIODevice::Append); job = m_proxy->annotate(QUrl::fromLocalFile(mercurialTest_BaseDir + mercurialTest_FileName), revision); verifyJobSucceed(job); results = job->fetchResults().toList(); QCOMPARE(results.size(), 3); auto commit2 = results[2].value(); QCOMPARE(commit2.text(), QStringLiteral("commit 2 content (temporary)")); QCOMPARE(commit2.lineNumber(), 2); QVERIFY(commit2.date().isValid()); QCOMPARE(commit2.revision().revisionValue().toLongLong(), 2); QCOMPARE(commit2.author(), QStringLiteral("not.committed.yet")); QCOMPARE(commit2.commitMessage(), QStringLiteral("Not Committed Yet")); // Check that commit 2 is stripped and mercurialTest_FileName is still modified job = m_proxy->status({QUrl::fromLocalFile(mercurialTest_BaseDir + mercurialTest_FileName)}, KDevelop::IBasicVersionControl::Recursive); verifyJobSucceed(job); auto statusResults = job->fetchResults().toList(); QCOMPARE(statusResults.size(), 1); auto status = statusResults[0].value(); QCOMPARE(status.url(), QUrl::fromLocalFile(mercurialTest_BaseDir + mercurialTest_FileName)); QCOMPARE(status.state(), VcsStatusInfo::ItemModified); } void MercurialTest::testDiff() { repoInit(); addFiles(); commitFiles(); writeToFile(mercurialTest_BaseDir + mercurialTest_FileName, "commit 2 content (temporary)", QIODevice::Append); VcsRevision srcrev = VcsRevision::createSpecialRevision(VcsRevision::Base); VcsRevision dstrev = VcsRevision::createSpecialRevision(VcsRevision::Working); VcsJob* j = m_proxy->diff(QUrl::fromLocalFile(mercurialTest_BaseDir), srcrev, dstrev, VcsDiff::DiffUnified, IBasicVersionControl::Recursive); verifyJobSucceed(j); KDevelop::VcsDiff d = j->fetchResults().value(); QCOMPARE(d.baseDiff().toLocalFile(), mercurialTest_BaseDir.left(mercurialTest_BaseDir.size() - 1)); QVERIFY(d.diff().contains(QUrl::fromLocalFile(mercurialTest_BaseDir + mercurialTest_FileName).toLocalFile())); } void MercurialTest::testAnnotateFailed() { repoInit(); addFiles(); commitFiles(); auto verifyAnnotateJobFailed = [this](MercurialAnnotateJob::TestCase test) { VcsRevision revision; revision.setRevisionValue(0, KDevelop::VcsRevision::GlobalNumber); auto job = m_proxy->annotate(QUrl::fromLocalFile(mercurialTest_BaseDir + mercurialTest_FileName), revision); auto annotateJob = dynamic_cast(job); QVERIFY(annotateJob); annotateJob->m_testCase = test; QVERIFY(job->exec()); QVERIFY(job->status() == KDevelop::VcsJob::JobFailed); }; verifyAnnotateJobFailed(MercurialAnnotateJob::TestCase::Status); //verifyAnnotateJobFailed(MercurialAnnotateJob::TestCase::Commit); verifyAnnotateJobFailed(MercurialAnnotateJob::TestCase::Annotate); verifyAnnotateJobFailed(MercurialAnnotateJob::TestCase::Log); //verifyAnnotateJobFailed(MercurialAnnotateJob::TestCase::Strip); } +void MercurialTest::testHeads() +{ + repoInit(); + addFiles(); + commitFiles(); + + VcsRevision revision; + revision.setRevisionValue(0, KDevelop::VcsRevision::GlobalNumber); + auto job = m_proxy->checkoutHead(QUrl::fromLocalFile(mercurialTest_BaseDir), revision); + verifyJobSucceed(job); + + writeToFile(mercurialTest_BaseDir + mercurialTest_FileName, "new head content", QIODevice::Append); + + auto j = m_proxy->commit(QString("new head"), {QUrl::fromLocalFile(mercurialTest_BaseDir)}, KDevelop::IBasicVersionControl::Recursive); + verifyJobSucceed(j); + + QScopedPointer headsModel(new MercurialHeadsModel(m_proxy, QUrl::fromLocalFile(mercurialTest_BaseDir), nullptr)); + headsModel->update(); + + QSignalSpy waiter(headsModel.data(), &MercurialHeadsModel::updateComplete); + QVERIFY(waiter.wait()); + QVERIFY(waiter.count() == 1); + + QCOMPARE(headsModel->rowCount(), 2); + auto rev2 = headsModel->data(headsModel->index(0, VcsBasicEventModel::RevisionColumn)).toLongLong(); + auto rev1 = headsModel->data(headsModel->index(1, VcsBasicEventModel::RevisionColumn)).toLongLong(); + QCOMPARE(rev2, 2); + QCOMPARE(rev1, 1); +} + QTEST_MAIN(MercurialTest) diff --git a/tests/test_mercurial.h b/tests/test_mercurial.h index ae51010..435aad0 100644 --- a/tests/test_mercurial.h +++ b/tests/test_mercurial.h @@ -1,64 +1,65 @@ /* This file is part of KDevelop Copyright 2008 Evgeniy Ivanov Copyright 2009 Fabian Wiesel 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 class MercurialPlugin; namespace KDevelop { class TestCore; } class MercurialTest: public QObject { Q_OBJECT private: void repoInit(); void addFiles(); void commitFiles(); void cloneRepository(); private slots: void testInit(); void testAdd(); void testCommit(); void testBranching(); void testRevisionHistory(); void testAnnotate(); void testDiff(); void testAnnotateFailed(); + void testHeads(); void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); private: MercurialPlugin *m_proxy; KDevelop::TestCore *m_testCore; void removeTempDirs(); }; diff --git a/ui/mercurialheadswidget.cpp b/ui/mercurialheadswidget.cpp index ecf255f..a310502 100644 --- a/ui/mercurialheadswidget.cpp +++ b/ui/mercurialheadswidget.cpp @@ -1,87 +1,76 @@ /*************************************************************************** * 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 "mercurialheadswidget.h" #include #include #include #include #include #include using namespace KDevelop; MercurialHeadsWidget::MercurialHeadsWidget(MercurialPlugin *plugin, const QUrl &url) : QDialog(), m_ui(new Ui::MercurialHeadsWidget), m_plugin(plugin), m_url(url) { m_ui->setupUi(this); - m_headsModel = new MercurialHeadsModel(plugin, VcsRevision(), url, this); + m_headsModel = new MercurialHeadsModel(plugin, url, this); m_ui->headsTableView->setModel(static_cast(m_headsModel)); connect(m_ui->checkoutPushButton, &QPushButton::clicked, this, &MercurialHeadsWidget::checkoutRequested); connect(m_ui->mergePushButton, &QPushButton::clicked, this, &MercurialHeadsWidget::mergeRequested); setWindowTitle(i18n("Mercurial Heads (%1)", m_url.toLocalFile())); updateModel(); } void MercurialHeadsWidget::updateModel() { - VcsJob *identifyJob = m_plugin->identify(m_url); - connect(identifyJob, &VcsJob::resultsReady, this, &MercurialHeadsWidget::identifyReceived); - ICore::self()->runController()->registerJob(identifyJob); -} - -void MercurialHeadsWidget::identifyReceived(VcsJob *job) -{ - QList currentHeads; - foreach (const QVariant & value, job->fetchResults().toList()) { - currentHeads << value.value(); - } - m_headsModel->setCurrentHeads(currentHeads); + m_headsModel->update(); } void MercurialHeadsWidget::checkoutRequested() { const QModelIndex &selection = m_ui->headsTableView->currentIndex(); if (!selection.isValid()) { return; } VcsJob *job = m_plugin->checkoutHead(m_url, m_headsModel->eventForIndex(selection).revision()); connect(job, &VcsJob::resultsReady, this, &MercurialHeadsWidget::updateModel); ICore::self()->runController()->registerJob(job); } void MercurialHeadsWidget::mergeRequested() { const QModelIndex &selection = m_ui->headsTableView->currentIndex(); if (!selection.isValid()) { return; } VcsJob *job = m_plugin->mergeWith(m_url, m_headsModel->eventForIndex(selection).revision()); connect(job, &VcsJob::resultsReady, this, &MercurialHeadsWidget::updateModel); ICore::self()->runController()->registerJob(job); } diff --git a/ui/mercurialheadswidget.h b/ui/mercurialheadswidget.h index 4f300d2..78ab846 100644 --- a/ui/mercurialheadswidget.h +++ b/ui/mercurialheadswidget.h @@ -1,59 +1,59 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #ifndef MERCURIALHEADSWIDGET_H #define MERCURIALHEADSWIDGET_H #include #include #include "ui_mercurialheadswidget.h" class MercurialPlugin; namespace KDevelop { class VcsJob; } class MercurialHeadsModel; class MercurialHeadsWidget : public QDialog { Q_OBJECT public: explicit MercurialHeadsWidget(MercurialPlugin *plugin, const QUrl &url); private slots: void updateModel(); - void identifyReceived(KDevelop::VcsJob *job); + void checkoutRequested(); void mergeRequested(); private: Ui::MercurialHeadsWidget *m_ui; MercurialHeadsModel *m_headsModel; MercurialPlugin *m_plugin; const QUrl &m_url; }; #endif // MERCURIALHEADSWIDGET_H