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 <orgads@gmail.com>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
hjk
2024-02-07 16:13:22 +01:00
parent 5a643b1c0a
commit 0b44cc5589
6 changed files with 134 additions and 176 deletions

View File

@@ -14,44 +14,40 @@
#include <QRegularExpression>
#include <QStringList>
/*!
\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 <repository>/.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<FilePath, TopicData> 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 <repository>/.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 &)
{
}

View File

@@ -8,16 +8,15 @@
#include <utils/id.h>
#include <utils/filepath.h>
#include <QDateTime>
#include <QFlags>
#include <QHash>
#include <QObject>
#include <QString>
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<Utils::FilePath, TopicData> 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(const Utils::FilePath &)>;
Utils::FilePath trackFile(const Utils::FilePath &repository);
void setTopicFileTracker(const FileTracker &fileTracker);
using TopicRefresher = std::function<QString(const Utils::FilePath &)>;
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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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