From 0b44cc5589a0fb94271131bfa2cddfc61b58bced Mon Sep 17 00:00:00 2001 From: hjk Date: Wed, 7 Feb 2024 16:13:22 +0100 Subject: [PATCH] Vcs: Hide topic cache in new IVersionControl pimpl Potentially more stable api long-term. Also simplifies the user side code a bit. Change-Id: I6913e27e2a5dc14907e72f252081cdbed34842a3 Reviewed-by: Orgad Shaneh Reviewed-by: --- src/plugins/coreplugin/iversioncontrol.cpp | 160 ++++++++++++-------- src/plugins/coreplugin/iversioncontrol.h | 38 ++--- src/plugins/fossil/fossilplugin.cpp | 25 +-- src/plugins/git/gitplugin.cpp | 28 +--- src/plugins/mercurial/mercurialplugin.cpp | 24 +-- src/plugins/subversion/subversionplugin.cpp | 35 +---- 6 files changed, 134 insertions(+), 176 deletions(-) diff --git a/src/plugins/coreplugin/iversioncontrol.cpp b/src/plugins/coreplugin/iversioncontrol.cpp index 4cbc8050a26..f70e4be492e 100644 --- a/src/plugins/coreplugin/iversioncontrol.cpp +++ b/src/plugins/coreplugin/iversioncontrol.cpp @@ -14,44 +14,40 @@ #include #include -/*! - \class Core::IVersionControl::TopicCache - \inheaderfile coreplugin/iversioncontrol.h - \inmodule QtCreator - - \brief The TopicCache class stores a cache which maps a directory to a topic. - - A VCS topic is typically the current active branch name, but it can also have other - values (for example the latest tag) when there is no active branch. - - It is displayed: - \list - \li In the project tree, next to each root project - corresponding to the project. - \li In the main window title - corresponding to the current active editor. - \endlist - - In order to enable topic display, an IVersionControl subclass needs to create - an instance of the TopicCache subclass with appropriate overrides for its - pure virtual functions, and pass this instance to IVersionControl's constructor. - - The cache tracks a file in the repository, which is expected to change when the - topic changes. When the file is modified, the cache is refreshed. - For example: for Git this file is typically /.git/HEAD - */ - -/*! - \fn Utils::FilePath Core::IVersionControl::TopicCache::trackFile(const Utils::FilePath &repository) - Returns the path to the file that invalidates the cache for \a repository when - the file is modified. - - \fn QString Core::IVersionControl::TopicCache::refreshTopic(const Utils::FilePath &repository) - Returns the current topic for \a repository. - */ - using namespace Utils; namespace Core { +namespace Internal { + +class TopicData +{ +public: + QDateTime timeStamp; + QString topic; +}; + +class IVersionControlPrivate +{ +public: + IVersionControl::FileTracker m_fileTracker; + IVersionControl::TopicRefresher m_topicRefresher; + QHash m_topicCache; +}; + +} // Internal + +IVersionControl::IVersionControl() + : d(new Internal::IVersionControlPrivate) +{ + Core::VcsManager::addVersionControl(this); +} + +IVersionControl::~IVersionControl() +{ + delete d; +} + QString IVersionControl::vcsOpenText() const { return Tr::tr("Open with VCS (%1)").arg(displayName()); @@ -111,24 +107,83 @@ IVersionControl::RepoUrl IVersionControl::getRepoUrl(const QString &location) co return RepoUrl(location); } -void IVersionControl::setTopicCache(TopicCache *topicCache) +FilePath IVersionControl::trackFile(const FilePath &repository) { - m_topicCache = topicCache; + QTC_ASSERT(d->m_fileTracker, return {}); + return d->m_fileTracker(repository); } +QString IVersionControl::refreshTopic(const FilePath &repository) +{ + QTC_ASSERT(d->m_topicRefresher, return {}); + return d->m_topicRefresher(repository); +} + +/*! + Returns the topic for repository under \a topLevel. + + A VCS topic is typically the current active branch name, but it can also have other + values (for example the latest tag) when there is no active branch. + + It is displayed: + \list + \li In the project tree, next to each root project - corresponding to the project. + \li In the main window title - corresponding to the current active editor. + \endlist + + In order to enable topic display, an IVersionControl subclass needs to create + an instance of the TopicCache subclass with appropriate overrides for its + pure virtual functions, and pass this instance to IVersionControl's constructor. + + The cache tracks a file in the repository, which is expected to change when the + topic changes. When the file is modified, the cache is refreshed. + For example: for Git this file is typically /.git/HEAD + + The base implementation features a cache. If the cache for \a topLevel is valid, + it will be used. Otherwise it will be refreshed using the items provided by + \c setTopicFileTracker() and \c setTopicRefresher(). + + \sa setTopicFileTracker(), setTopicRefresher(). + */ + QString IVersionControl::vcsTopic(const FilePath &topLevel) { - return m_topicCache ? m_topicCache->topic(topLevel) : QString(); + QTC_ASSERT(!topLevel.isEmpty(), return QString()); + Internal::TopicData &data = d->m_topicCache[topLevel]; + const FilePath file = trackFile(topLevel); + + if (file.isEmpty()) + return QString(); + const QDateTime lastModified = file.lastModified(); + if (lastModified == data.timeStamp) + return data.topic; + data.timeStamp = lastModified; + return data.topic = refreshTopic(topLevel); } -IVersionControl::IVersionControl() +/*! + Provides the \a fileTracker function object for use in \c vscTopic() cache handling. + + The parameter object takes a repository as input and returns the file + that should trigger topic refresh (e.g. .git/HEAD for Git). + + Modification of this file will invalidate the internal topic cache for the repository. +*/ + +void IVersionControl::setTopicFileTracker(const FileTracker &fileTracker) { - Core::VcsManager::addVersionControl(this); + d->m_fileTracker = fileTracker; } -IVersionControl::~IVersionControl() +/*! + Provides the \a topicRefresher function object for use in \c vscTopic() cache handling. + + The parameter object takes a repository as input and returns its current topic. + */ + +void IVersionControl::setTopicRefresher(const TopicRefresher &topicRefresher) { - delete m_topicCache; + d->m_topicRefresher = topicRefresher; } FilePaths IVersionControl::unmanagedFiles(const FilePaths &filePaths) const @@ -143,29 +198,6 @@ IVersionControl::OpenSupportMode IVersionControl::openSupportMode(const FilePath Q_UNUSED(filePath) return NoOpen; } - -IVersionControl::TopicCache::~TopicCache() = default; - -/*! - Returns the topic for repository under \a topLevel. - - If the cache for \a topLevel is valid, it will be used. Otherwise it will be refreshed. - */ -QString IVersionControl::TopicCache::topic(const FilePath &topLevel) -{ - QTC_ASSERT(!topLevel.isEmpty(), return QString()); - TopicData &data = m_cache[topLevel]; - const FilePath file = trackFile(topLevel); - - if (file.isEmpty()) - return QString(); - const QDateTime lastModified = file.lastModified(); - if (lastModified == data.timeStamp) - return data.topic; - data.timeStamp = lastModified; - return data.topic = refreshTopic(topLevel); -} - void IVersionControl::fillLinkContextMenu(QMenu *, const FilePath &, const QString &) { } diff --git a/src/plugins/coreplugin/iversioncontrol.h b/src/plugins/coreplugin/iversioncontrol.h index e97749ca1b2..31e35437770 100644 --- a/src/plugins/coreplugin/iversioncontrol.h +++ b/src/plugins/coreplugin/iversioncontrol.h @@ -8,16 +8,15 @@ #include #include -#include #include -#include #include -#include QT_FORWARD_DECLARE_CLASS(QMenu); namespace Core { +namespace Internal { class IVersionControlPrivate; } + class CORE_EXPORT IVersionControl : public QObject { Q_OBJECT @@ -43,28 +42,6 @@ public: OpenMandatory /*!< Files must always be opened by the VCS */ }; - class CORE_EXPORT TopicCache - { - public: - virtual ~TopicCache(); - QString topic(const Utils::FilePath &topLevel); - - protected: - virtual Utils::FilePath trackFile(const Utils::FilePath &repository) = 0; - virtual QString refreshTopic(const Utils::FilePath &repository) = 0; - - private: - class TopicData - { - public: - QDateTime timeStamp; - QString topic; - }; - - QHash m_cache; - - }; - IVersionControl(); ~IVersionControl() override; @@ -218,7 +195,14 @@ public: }; virtual RepoUrl getRepoUrl(const QString &location) const; - void setTopicCache(TopicCache *topicCache); + // Topic cache + using FileTracker = std::function; + Utils::FilePath trackFile(const Utils::FilePath &repository); + void setTopicFileTracker(const FileTracker &fileTracker); + + using TopicRefresher = std::function; + QString refreshTopic(const Utils::FilePath &repository); + void setTopicRefresher(const TopicRefresher &topicRefresher); signals: void repositoryChanged(const Utils::FilePath &repository); @@ -226,7 +210,7 @@ signals: void configurationChanged(); private: - TopicCache *m_topicCache = nullptr; + Internal::IVersionControlPrivate *d; }; } // namespace Core diff --git a/src/plugins/fossil/fossilplugin.cpp b/src/plugins/fossil/fossilplugin.cpp index b09ffda013b..25fcdfa923a 100644 --- a/src/plugins/fossil/fossilplugin.cpp +++ b/src/plugins/fossil/fossilplugin.cpp @@ -61,23 +61,6 @@ using namespace std::placeholders; namespace Fossil::Internal { -class FossilTopicCache final : public IVersionControl::TopicCache -{ -public: - FossilTopicCache() = default; - -protected: - FilePath trackFile(const FilePath &repository) final - { - return repository.pathAppended(Constants::FOSSILREPO); - } - - QString refreshTopic(const FilePath &repository) final - { - return fossilClient().synchronousTopic(repository); - } -}; - class FossilPluginPrivate final : public VersionControlBase { public: @@ -222,7 +205,13 @@ FossilPluginPrivate::FossilPluginPrivate() { Context context(Constants::FOSSIL_CONTEXT); - setTopicCache(new FossilTopicCache); + setTopicFileTracker([](const FilePath &repository) { + return repository.pathAppended(Constants::FOSSILREPO); + }); + setTopicRefresher([](const FilePath &repository) { + return fossilClient().synchronousTopic(repository); + }); + connect(&fossilClient(), &VcsBaseClient::changed, this, &FossilPluginPrivate::changed); m_commandLocator = new CommandLocator("Fossil", "fossil", "fossil", this); diff --git a/src/plugins/git/gitplugin.cpp b/src/plugins/git/gitplugin.cpp index 9535f14128d..915796bdbf0 100644 --- a/src/plugins/git/gitplugin.cpp +++ b/src/plugins/git/gitplugin.cpp @@ -382,25 +382,6 @@ public: static GitPluginPrivate *dd = nullptr; -class GitTopicCache : public IVersionControl::TopicCache -{ -public: - GitTopicCache() {} - -protected: - FilePath trackFile(const FilePath &repository) override - { - const FilePath gitDir = gitClient().findGitDirForRepository(repository); - return gitDir.isEmpty() ? FilePath() : gitDir / "HEAD"; - } - - QString refreshTopic(const FilePath &repository) override - { - emit dd->repositoryChanged(repository); - return gitClient().synchronousTopic(repository); - } -}; - GitPluginPrivate::~GitPluginPrivate() { cleanCommitMessageFile(); @@ -560,7 +541,14 @@ GitPluginPrivate::GitPluginPrivate() { dd = this; - setTopicCache(new GitTopicCache); + setTopicFileTracker([](const FilePath &repository) { + const FilePath gitDir = gitClient().findGitDirForRepository(repository); + return gitDir.isEmpty() ? FilePath() : gitDir / "HEAD"; + }); + setTopicRefresher([this](const FilePath &repository) { + emit repositoryChanged(repository); + return gitClient().synchronousTopic(repository); + }); m_fileActions.reserve(10); m_projectActions.reserve(10); diff --git a/src/plugins/mercurial/mercurialplugin.cpp b/src/plugins/mercurial/mercurialplugin.cpp index 1fa1fc8155e..91c25139d9f 100644 --- a/src/plugins/mercurial/mercurialplugin.cpp +++ b/src/plugins/mercurial/mercurialplugin.cpp @@ -51,23 +51,6 @@ using namespace std::placeholders; namespace Mercurial::Internal { -class MercurialTopicCache : public Core::IVersionControl::TopicCache -{ -public: - MercurialTopicCache() = default; - -protected: - FilePath trackFile(const FilePath &repository) override - { - return repository.pathAppended(".hg/branch"); - } - - QString refreshTopic(const FilePath &repository) override - { - return mercurialClient().branchQuerySync(repository.toString()); - } -}; - class MercurialPluginPrivate final : public VcsBase::VersionControlBase { public: @@ -203,7 +186,12 @@ MercurialPluginPrivate::MercurialPluginPrivate() [] { return new CommitEditor; } }); - setTopicCache(new MercurialTopicCache); + setTopicFileTracker([](const FilePath &repository) { + return repository.pathAppended(".hg/branch"); + }); + setTopicRefresher([](const FilePath &repository) { + return mercurialClient().branchQuerySync(repository.toString()); + }); Core::Context context(Constants::MERCURIAL_CONTEXT); diff --git a/src/plugins/subversion/subversionplugin.cpp b/src/plugins/subversion/subversionplugin.cpp index 95a774e1f83..cab80c64d0d 100644 --- a/src/plugins/subversion/subversionplugin.cpp +++ b/src/plugins/subversion/subversionplugin.cpp @@ -126,24 +126,6 @@ static inline QStringList svnDirectories() return rc; } -class SubversionPluginPrivate; - -class SubversionTopicCache : public Core::IVersionControl::TopicCache -{ -public: - SubversionTopicCache(SubversionPluginPrivate *plugin) : - m_plugin(plugin) - { } - -protected: - FilePath trackFile(const FilePath &repository) override; - - QString refreshTopic(const FilePath &repository) override; - -private: - SubversionPluginPrivate *m_plugin; -}; - class SubversionPluginPrivate final : public VcsBase::VersionControlBase { public: @@ -312,7 +294,12 @@ SubversionPluginPrivate::SubversionPluginPrivate() { dd = this; - setTopicCache(new SubversionTopicCache(this)); + setTopicFileTracker([this](const FilePath &repository) { + return FilePath::fromString(monitorFile(repository)); + }); + setTopicRefresher([this](const FilePath &repository) { + return synchronousTopic(repository); + }); using namespace Constants; using namespace Core::Constants; @@ -1153,16 +1140,6 @@ VcsCommand *SubversionPluginPrivate::createInitialCheckoutCommand(const QString return command; } -FilePath SubversionTopicCache::trackFile(const FilePath &repository) -{ - return FilePath::fromString(m_plugin->monitorFile(repository)); -} - -QString SubversionTopicCache::refreshTopic(const FilePath &repository) -{ - return m_plugin->synchronousTopic(repository); -} - #ifdef WITH_TESTS