forked from qt-creator/qt-creator
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:
@@ -63,6 +63,22 @@ FilePaths IVersionControl::additionalToolsPath() const
|
||||
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)
|
||||
{
|
||||
if (location.isEmpty())
|
||||
|
@@ -92,6 +92,22 @@ public:
|
||||
* Returns true is the VCS is configured to run.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -207,6 +223,7 @@ public:
|
||||
signals:
|
||||
void repositoryChanged(const Utils::FilePath &repository);
|
||||
void filesChanged(const QStringList &files);
|
||||
void updateFileStatus(const Utils::FilePath &repository, const QStringList &files);
|
||||
void configurationChanged();
|
||||
|
||||
private:
|
||||
|
@@ -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
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -822,10 +833,14 @@ GitClient &gitClient()
|
||||
|
||||
GitClient::GitClient()
|
||||
: VcsBase::VcsBaseClientImpl(&Internal::settings())
|
||||
, m_timer(new QTimer)
|
||||
{
|
||||
m_gitQtcEditor = QString::fromLatin1("\"%1\" -client -block -pid %2")
|
||||
.arg(QCoreApplication::applicationFilePath())
|
||||
.arg(QCoreApplication::applicationPid());
|
||||
|
||||
connect(m_timer.get(), &QTimer::timeout, this, &GitClient::updateModificationInfos);
|
||||
m_timer->setInterval(10000); // 10s
|
||||
}
|
||||
|
||||
GitClient::~GitClient() = default;
|
||||
@@ -899,6 +914,83 @@ FilePaths GitClient::unmanagedFiles(const FilePaths &filePaths) const
|
||||
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
|
||||
{
|
||||
// Set default commit encoding to 'UTF-8', when it's not set,
|
||||
|
@@ -109,6 +109,12 @@ public:
|
||||
PushAction m_pushAction = NoPush;
|
||||
};
|
||||
|
||||
struct ModificationInfo
|
||||
{
|
||||
Utils::FilePath rootPath;
|
||||
QSet<QString> modifiedFiles;
|
||||
};
|
||||
|
||||
GitClient();
|
||||
~GitClient();
|
||||
|
||||
@@ -124,6 +130,10 @@ public:
|
||||
Utils::FilePath findGitDirForRepository(const Utils::FilePath &repositoryDir) const;
|
||||
bool managesFile(const Utils::FilePath &workingDirectory, const QString &fileName) 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 diffFiles(const Utils::FilePath &workingDirectory,
|
||||
@@ -373,6 +383,7 @@ private:
|
||||
const Utils::FilePath &oldGitBinDir) const;
|
||||
bool cleanList(const Utils::FilePath &workingDirectory, const QString &modulePath,
|
||||
const QString &flag, QStringList *files, QString *errorMessage);
|
||||
void updateModificationInfos();
|
||||
|
||||
enum ContinueCommandMode {
|
||||
ContinueOnly,
|
||||
@@ -390,6 +401,8 @@ private:
|
||||
|
||||
QString m_gitQtcEditor;
|
||||
QMap<Utils::FilePath, StashInfo> m_stashInfo;
|
||||
QHash<Utils::FilePath, ModificationInfo> m_modifInfos;
|
||||
std::unique_ptr<QTimer> m_timer;
|
||||
QString m_diffCommit;
|
||||
Utils::FilePaths m_updatedSubmodules;
|
||||
bool m_disableEditor = false;
|
||||
|
@@ -155,6 +155,9 @@ public:
|
||||
FilePaths unmanagedFiles(const FilePaths &filePaths) 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 vcsOpen(const FilePath &filePath) final;
|
||||
bool vcsAdd(const FilePath &filePath) final;
|
||||
@@ -1714,6 +1717,22 @@ bool GitPluginPrivate::isConfigured() const
|
||||
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
|
||||
{
|
||||
if (!isConfigured())
|
||||
@@ -1830,6 +1849,11 @@ void emitRepositoryChanged(const FilePath &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)
|
||||
{
|
||||
dd->startRebaseFromCommit(workingDirectory, commit);
|
||||
|
@@ -22,6 +22,7 @@ bool isCommitEditorOpen();
|
||||
|
||||
void emitFilesChanged(const QStringList &);
|
||||
void emitRepositoryChanged(const Utils::FilePath &);
|
||||
void emitFileStatusChanged(const Utils::FilePath &repository, const QStringList &files);
|
||||
void startRebaseFromCommit(const Utils::FilePath &workingDirectory, const QString &commit);
|
||||
void manageRemotes();
|
||||
void initRepository();
|
||||
|
@@ -241,6 +241,8 @@ QVariant FlatModel::data(const QModelIndex &index, int role) const
|
||||
return font;
|
||||
}
|
||||
case Qt::ForegroundRole:
|
||||
if (fileNode && fileNode->hasModification())
|
||||
return Utils::creatorColor(Utils::Theme::VcsBase_FileModified_TextColor);
|
||||
return node->isEnabled() ? QVariant()
|
||||
: Utils::creatorColor(Utils::Theme::TextColorDisabled);
|
||||
case Project::FilePathRole:
|
||||
@@ -447,12 +449,39 @@ void FlatModel::handleProjectAdded(Project *project)
|
||||
parsingStateChanged(project);
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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
|
||||
|
@@ -85,6 +85,7 @@ private:
|
||||
void rebuildModel();
|
||||
void addFolderNode(WrapperNode *parent, FolderNode *folderNode, QSet<Node *> *seen);
|
||||
bool trimEmptyDirectories(WrapperNode *parent);
|
||||
void updateVCStatusFor(const Utils::FilePath root, const QStringList &files);
|
||||
|
||||
ExpandData expandDataForNode(const Node *node) const;
|
||||
void loadExpandData();
|
||||
|
@@ -261,6 +261,22 @@ void FileNode::setHasError(bool error) const
|
||||
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
|
||||
{
|
||||
return m_useUnavailableMarker;
|
||||
|
@@ -201,6 +201,8 @@ public:
|
||||
void setHasError(const bool error);
|
||||
void setHasError(const bool error) const;
|
||||
|
||||
bool hasModification() const;
|
||||
|
||||
QIcon icon() const;
|
||||
void setIcon(const QIcon icon);
|
||||
|
||||
|
Reference in New Issue
Block a user