Git: Colorize modified files in projects view

Fixes: QTCREATORBUG-8857
Change-Id: I9922f731cf3c7a7f25a72cbe6eab64391f4f8054
Reviewed-by: André Hartmann <aha_1980@gmx.de>
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
This commit is contained in:
Renaud Guezennec
2024-07-18 18:33:33 +02:00
parent b13a2804f0
commit 64952cb511
10 changed files with 211 additions and 0 deletions

View File

@@ -63,6 +63,22 @@ FilePaths IVersionControl::additionalToolsPath() const
return {}; return {};
} }
bool IVersionControl::hasModification(const FilePath &path) const
{
Q_UNUSED(path)
return false;
}
void IVersionControl::monitorDirectory(const Utils::FilePath &path)
{
Q_UNUSED(path)
}
void IVersionControl::stopMonitoringDirectory(const Utils::FilePath &path)
{
Q_UNUSED(path)
}
IVersionControl::RepoUrl::RepoUrl(const QString &location) IVersionControl::RepoUrl::RepoUrl(const QString &location)
{ {
if (location.isEmpty()) if (location.isEmpty())

View File

@@ -92,6 +92,22 @@ public:
* Returns true is the VCS is configured to run. * Returns true is the VCS is configured to run.
*/ */
virtual bool isConfigured() const = 0; virtual bool isConfigured() const = 0;
/*!
* Returns true is the file has modification compare to version control
*/
virtual bool hasModification(const Utils::FilePath &path) const;
/*!
* Starts monitoring modified files inside path
*/
virtual void monitorDirectory(const Utils::FilePath &path);
/*!
* Stops monitoring modified files inside path
*/
virtual void stopMonitoringDirectory(const Utils::FilePath &path);
/*! /*!
* Called to query whether a VCS supports the respective operations. * Called to query whether a VCS supports the respective operations.
* *
@@ -207,6 +223,7 @@ public:
signals: signals:
void repositoryChanged(const Utils::FilePath &repository); void repositoryChanged(const Utils::FilePath &repository);
void filesChanged(const QStringList &files); void filesChanged(const QStringList &files);
void updateFileStatus(const Utils::FilePath &repository, const QStringList &files);
void configurationChanged(); void configurationChanged();
private: private:

View File

@@ -137,6 +137,17 @@ static void stage(DiffEditorController *diffController, const QString &patch, bo
} }
} }
///////////////////////////////
static QList<FilePath> submoduleDataToAbsolutePath(const SubmoduleDataMap &submodules,
const FilePath &rootDir)
{
QList<FilePath> res;
std::transform(std::begin(submodules), std::end(submodules), std::back_inserter(res),
[rootDir](const SubmoduleData &data) { return rootDir.pathAppended(data.dir); });
return res;
}
class GitBaseDiffEditorController : public VcsBaseDiffEditorController class GitBaseDiffEditorController : public VcsBaseDiffEditorController
{ {
Q_OBJECT Q_OBJECT
@@ -822,10 +833,14 @@ GitClient &gitClient()
GitClient::GitClient() GitClient::GitClient()
: VcsBase::VcsBaseClientImpl(&Internal::settings()) : VcsBase::VcsBaseClientImpl(&Internal::settings())
, m_timer(new QTimer)
{ {
m_gitQtcEditor = QString::fromLatin1("\"%1\" -client -block -pid %2") m_gitQtcEditor = QString::fromLatin1("\"%1\" -client -block -pid %2")
.arg(QCoreApplication::applicationFilePath()) .arg(QCoreApplication::applicationFilePath())
.arg(QCoreApplication::applicationPid()); .arg(QCoreApplication::applicationPid());
connect(m_timer.get(), &QTimer::timeout, this, &GitClient::updateModificationInfos);
m_timer->setInterval(10000); // 10s
} }
GitClient::~GitClient() = default; GitClient::~GitClient() = default;
@@ -899,6 +914,83 @@ FilePaths GitClient::unmanagedFiles(const FilePaths &filePaths) const
return res; return res;
} }
bool GitClient::hasModification(const Utils::FilePath &workingDirectory,
const Utils::FilePath &fileName) const
{
const ModificationInfo &info = m_modifInfos[workingDirectory];
QString fileNameFromRoot = fileName.absoluteFilePath().toString();
int length = workingDirectory.toString().size();
fileNameFromRoot.remove(0, length + 1);
return info.modifiedFiles.contains(fileNameFromRoot);
}
void GitClient::stopMonitoring(const Utils::FilePath &path)
{
const FilePath directory = path;
// Submodule management
QList<FilePath> subPaths = submoduleDataToAbsolutePath(submoduleList(directory), directory);
std::for_each(std::begin(subPaths), std::end(subPaths),
[this](const FilePath &subModule) { m_modifInfos.remove(subModule); });
m_modifInfos.remove(directory);
if (m_modifInfos.isEmpty())
m_timer->stop();
}
void GitClient::monitorDirectory(const Utils::FilePath &path)
{
const FilePath directory = path;
if (directory.isEmpty())
return;
m_modifInfos.insert(directory, {directory, {}});
// Submodule management
QList<FilePath> subPaths = submoduleDataToAbsolutePath(submoduleList(directory), directory);
std::for_each(std::begin(subPaths), std::end(subPaths), [this](const FilePath &subModule) {
m_modifInfos.insert(subModule, {subModule, {}});
});
if (!m_timer->isActive())
m_timer->start();
updateModificationInfos();
}
void GitClient::updateModificationInfos()
{
for (ModificationInfo &info : m_modifInfos) {
const FilePath &path = info.rootPath;
const auto command = [&info](const CommandResult &result){
const QStringList res = result.cleanedStdOut().split("\n", Qt::SkipEmptyParts);
QSet<QString> modifiedFiles;
for (const QString &line : res)
{
if (line.size() <= 3)
continue;
QString file = line;
// can't use stateFor() function from commitdata.cpp
static const QSet<QChar> gitStates{'M', 'A'};
if (gitStates.contains(line.at(0)) || gitStates.contains(line.at(1)))
modifiedFiles.insert(file.remove(0, 3).trimmed());
}
const QSet<QString> oldfiles = info.modifiedFiles;
info.modifiedFiles = modifiedFiles;
QStringList newList = modifiedFiles.values();
QStringList list = oldfiles.values();
std::sort(std::begin(list), std::end(list));
std::sort(std::begin(newList), std::end(newList));
QStringList statusChangedFiles;
std::set_symmetric_difference(std::begin(list), std::end(list),
std::begin(newList), std::end(newList),
std::back_inserter(statusChangedFiles));
emitFileStatusChanged(info.rootPath, statusChangedFiles);
};
vcsExecWithHandler(path, {"status", "-s", "--porcelain"}, this, command, RunFlags::NoOutput);
}
}
QTextCodec *GitClient::defaultCommitEncoding() const QTextCodec *GitClient::defaultCommitEncoding() const
{ {
// Set default commit encoding to 'UTF-8', when it's not set, // Set default commit encoding to 'UTF-8', when it's not set,

View File

@@ -109,6 +109,12 @@ public:
PushAction m_pushAction = NoPush; PushAction m_pushAction = NoPush;
}; };
struct ModificationInfo
{
Utils::FilePath rootPath;
QSet<QString> modifiedFiles;
};
GitClient(); GitClient();
~GitClient(); ~GitClient();
@@ -124,6 +130,10 @@ public:
Utils::FilePath findGitDirForRepository(const Utils::FilePath &repositoryDir) const; Utils::FilePath findGitDirForRepository(const Utils::FilePath &repositoryDir) const;
bool managesFile(const Utils::FilePath &workingDirectory, const QString &fileName) const; bool managesFile(const Utils::FilePath &workingDirectory, const QString &fileName) const;
Utils::FilePaths unmanagedFiles(const Utils::FilePaths &filePaths) const; Utils::FilePaths unmanagedFiles(const Utils::FilePaths &filePaths) const;
bool hasModification(const Utils::FilePath &workingDirectory,
const Utils::FilePath &fileName) const;
void monitorDirectory(const Utils::FilePath &path);
void stopMonitoring(const Utils::FilePath &path);
void diffFile(const Utils::FilePath &workingDirectory, const QString &fileName) const; void diffFile(const Utils::FilePath &workingDirectory, const QString &fileName) const;
void diffFiles(const Utils::FilePath &workingDirectory, void diffFiles(const Utils::FilePath &workingDirectory,
@@ -373,6 +383,7 @@ private:
const Utils::FilePath &oldGitBinDir) const; const Utils::FilePath &oldGitBinDir) const;
bool cleanList(const Utils::FilePath &workingDirectory, const QString &modulePath, bool cleanList(const Utils::FilePath &workingDirectory, const QString &modulePath,
const QString &flag, QStringList *files, QString *errorMessage); const QString &flag, QStringList *files, QString *errorMessage);
void updateModificationInfos();
enum ContinueCommandMode { enum ContinueCommandMode {
ContinueOnly, ContinueOnly,
@@ -390,6 +401,8 @@ private:
QString m_gitQtcEditor; QString m_gitQtcEditor;
QMap<Utils::FilePath, StashInfo> m_stashInfo; QMap<Utils::FilePath, StashInfo> m_stashInfo;
QHash<Utils::FilePath, ModificationInfo> m_modifInfos;
std::unique_ptr<QTimer> m_timer;
QString m_diffCommit; QString m_diffCommit;
Utils::FilePaths m_updatedSubmodules; Utils::FilePaths m_updatedSubmodules;
bool m_disableEditor = false; bool m_disableEditor = false;

View File

@@ -155,6 +155,9 @@ public:
FilePaths unmanagedFiles(const FilePaths &filePaths) const final; FilePaths unmanagedFiles(const FilePaths &filePaths) const final;
bool isConfigured() const final; bool isConfigured() const final;
bool hasModification(const Utils::FilePath &path) const final;
void monitorDirectory(const Utils::FilePath &path) final;
void stopMonitoringDirectory(const Utils::FilePath &path) final;
bool supportsOperation(Operation operation) const final; bool supportsOperation(Operation operation) const final;
bool vcsOpen(const FilePath &filePath) final; bool vcsOpen(const FilePath &filePath) final;
bool vcsAdd(const FilePath &filePath) final; bool vcsAdd(const FilePath &filePath) final;
@@ -1714,6 +1717,22 @@ bool GitPluginPrivate::isConfigured() const
return !gitClient().vcsBinary({}).isEmpty(); return !gitClient().vcsBinary({}).isEmpty();
} }
bool GitPluginPrivate::hasModification(const Utils::FilePath &path) const
{
const Utils::FilePath projectDir = gitClient().findRepositoryForDirectory(path.absolutePath());
return gitClient().hasModification(projectDir, path);
}
void GitPluginPrivate::monitorDirectory(const Utils::FilePath &path)
{
gitClient().monitorDirectory(gitClient().findRepositoryForDirectory(path));
}
void GitPluginPrivate::stopMonitoringDirectory(const Utils::FilePath &path)
{
gitClient().stopMonitoring(gitClient().findRepositoryForDirectory(path));
}
bool GitPluginPrivate::supportsOperation(Operation operation) const bool GitPluginPrivate::supportsOperation(Operation operation) const
{ {
if (!isConfigured()) if (!isConfigured())
@@ -1830,6 +1849,11 @@ void emitRepositoryChanged(const FilePath &r)
emit dd->repositoryChanged(r); emit dd->repositoryChanged(r);
} }
void emitFileStatusChanged(const FilePath &r, const QStringList &l)
{
emit dd->updateFileStatus(r, l);
}
void startRebaseFromCommit(const FilePath &workingDirectory, const QString &commit) void startRebaseFromCommit(const FilePath &workingDirectory, const QString &commit)
{ {
dd->startRebaseFromCommit(workingDirectory, commit); dd->startRebaseFromCommit(workingDirectory, commit);

View File

@@ -22,6 +22,7 @@ bool isCommitEditorOpen();
void emitFilesChanged(const QStringList &); void emitFilesChanged(const QStringList &);
void emitRepositoryChanged(const Utils::FilePath &); void emitRepositoryChanged(const Utils::FilePath &);
void emitFileStatusChanged(const Utils::FilePath &repository, const QStringList &files);
void startRebaseFromCommit(const Utils::FilePath &workingDirectory, const QString &commit); void startRebaseFromCommit(const Utils::FilePath &workingDirectory, const QString &commit);
void manageRemotes(); void manageRemotes();
void initRepository(); void initRepository();

View File

@@ -241,6 +241,8 @@ QVariant FlatModel::data(const QModelIndex &index, int role) const
return font; return font;
} }
case Qt::ForegroundRole: case Qt::ForegroundRole:
if (fileNode && fileNode->hasModification())
return Utils::creatorColor(Utils::Theme::VcsBase_FileModified_TextColor);
return node->isEnabled() ? QVariant() return node->isEnabled() ? QVariant()
: Utils::creatorColor(Utils::Theme::TextColorDisabled); : Utils::creatorColor(Utils::Theme::TextColorDisabled);
case Project::FilePathRole: case Project::FilePathRole:
@@ -447,12 +449,39 @@ void FlatModel::handleProjectAdded(Project *project)
parsingStateChanged(project); parsingStateChanged(project);
emit ProjectTree::instance()->nodeActionsChanged(); emit ProjectTree::instance()->nodeActionsChanged();
}); });
const FilePath &rootPath = project->rootProjectDirectory();
IVersionControl *vc = VcsManager::findVersionControlForDirectory(rootPath);
if (!vc)
return;
vc->monitorDirectory(rootPath);
connect(vc, &IVersionControl::updateFileStatus, this, &FlatModel::updateVCStatusFor);
addOrRebuildProjectModel(project); addOrRebuildProjectModel(project);
} }
void FlatModel::updateVCStatusFor(const Utils::FilePath root, const QStringList &files)
{
std::for_each(std::begin(files), std::end(files), [root, this](const QString &file) {
const Node *node = ProjectTree::nodeForFile(root.pathAppended(file));
if (!node)
return;
const QModelIndex index = indexForNode(node);
emit dataChanged(index, index, {Qt::ForegroundRole});
});
}
void FlatModel::handleProjectRemoved(Project *project) void FlatModel::handleProjectRemoved(Project *project)
{ {
destroyItem(nodeForProject(project)); destroyItem(nodeForProject(project));
if (!project)
return;
const FilePath &rootPath = project->rootProjectDirectory();
if (IVersionControl *vc = VcsManager::findVersionControlForDirectory(rootPath))
vc->stopMonitoringDirectory(rootPath);
} }
WrapperNode *FlatModel::nodeForProject(const Project *project) const WrapperNode *FlatModel::nodeForProject(const Project *project) const

View File

@@ -85,6 +85,7 @@ private:
void rebuildModel(); void rebuildModel();
void addFolderNode(WrapperNode *parent, FolderNode *folderNode, QSet<Node *> *seen); void addFolderNode(WrapperNode *parent, FolderNode *folderNode, QSet<Node *> *seen);
bool trimEmptyDirectories(WrapperNode *parent); bool trimEmptyDirectories(WrapperNode *parent);
void updateVCStatusFor(const Utils::FilePath root, const QStringList &files);
ExpandData expandDataForNode(const Node *node) const; ExpandData expandDataForNode(const Node *node) const;
void loadExpandData(); void loadExpandData();

View File

@@ -261,6 +261,22 @@ void FileNode::setHasError(bool error) const
m_hasError = error; m_hasError = error;
} }
bool FileNode::hasModification() const
{
static const QSet<FileType> forbidden{FileType::Unknown, FileType::App, FileType::Lib,
FileType::FileTypeSize};
if (forbidden.contains(fileType()))
return false;
const FilePath file = filePath();
const FilePath dir = file.absolutePath();
if (Core::IVersionControl *vc = Core::VcsManager::findVersionControlForDirectory(dir))
return vc->hasModification(file);
return false;
}
bool FileNode::useUnavailableMarker() const bool FileNode::useUnavailableMarker() const
{ {
return m_useUnavailableMarker; return m_useUnavailableMarker;

View File

@@ -201,6 +201,8 @@ public:
void setHasError(const bool error); void setHasError(const bool error);
void setHasError(const bool error) const; void setHasError(const bool error) const;
bool hasModification() const;
QIcon icon() const; QIcon icon() const;
void setIcon(const QIcon icon); void setIcon(const QIcon icon);