forked from qt-creator/qt-creator
Git: Colorize modified files in projects view
Fixes: QTCREATORBUG-8857 Change-Id: Ie52ef20d6559046eb709f34329250cecc33db3bc Reviewed-by: Orgad Shaneh <orgads@gmail.com> Reviewed-by: André Hartmann <aha_1980@gmx.de> Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
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 if the file has modification compared 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.
|
||||
*
|
||||
@@ -213,6 +229,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,15 @@ static void stage(DiffEditorController *diffController, const QString &patch, bo
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
static FilePaths submoduleDataToAbsolutePath(const SubmoduleDataMap &submodules,
|
||||
const FilePath &rootDir)
|
||||
{
|
||||
return Utils::transform<FilePaths>(submodules, [&rootDir](const SubmoduleData &data)
|
||||
{ return rootDir.pathAppended(data.dir); });
|
||||
}
|
||||
|
||||
class GitBaseDiffEditorController : public VcsBaseDiffEditorController
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -815,10 +824,15 @@ 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);
|
||||
using namespace std::chrono_literals;
|
||||
m_timer->setInterval(10s);
|
||||
}
|
||||
|
||||
GitClient::~GitClient() = default;
|
||||
@@ -892,6 +906,84 @@ 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];
|
||||
int length = workingDirectory.toString().size();
|
||||
const QString fileNameFromRoot = fileName.absoluteFilePath().path().mid(length + 1);
|
||||
return info.modifiedFiles.contains(fileNameFromRoot);
|
||||
}
|
||||
|
||||
void GitClient::stopMonitoring(const Utils::FilePath &path)
|
||||
{
|
||||
const FilePath directory = path;
|
||||
// Submodule management
|
||||
const QList<FilePath> subPaths = submoduleDataToAbsolutePath(submoduleList(directory), directory);
|
||||
for (const FilePath &subModule : subPaths)
|
||||
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
|
||||
const QList<FilePath> subPaths = submoduleDataToAbsolutePath(submoduleList(directory), directory);
|
||||
for (const FilePath &subModule : subPaths)
|
||||
m_modifInfos.insert(subModule, {subModule, {}});
|
||||
if (!m_timer->isActive())
|
||||
m_timer->start();
|
||||
updateModificationInfos();
|
||||
}
|
||||
|
||||
void GitClient::updateModificationInfos()
|
||||
{
|
||||
for (const ModificationInfo &infoTemp : std::as_const(m_modifInfos)) {
|
||||
const FilePath path = infoTemp.rootPath;
|
||||
|
||||
const auto command = [path, this](const CommandResult &result) {
|
||||
if (!m_modifInfos.contains(path))
|
||||
return;
|
||||
|
||||
ModificationInfo &info = m_modifInfos[path];
|
||||
|
||||
const QStringList res = result.cleanedStdOut().split("\n", Qt::SkipEmptyParts);
|
||||
QSet<QString> modifiedFiles;
|
||||
for (const QString &line : res) {
|
||||
if (line.size() <= 3)
|
||||
continue;
|
||||
|
||||
static const QSet<QChar> gitStates{'M', 'A'};
|
||||
|
||||
if (gitStates.contains(line.at(0)) || gitStates.contains(line.at(1)))
|
||||
modifiedFiles.insert(line.mid(3).trimmed());
|
||||
}
|
||||
|
||||
const QSet<QString> oldfiles = info.modifiedFiles;
|
||||
info.modifiedFiles = modifiedFiles;
|
||||
|
||||
QStringList newList = modifiedFiles.values();
|
||||
QStringList list = oldfiles.values();
|
||||
newList.sort();
|
||||
list.sort();
|
||||
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,
|
||||
|
@@ -119,6 +119,12 @@ public:
|
||||
PushAction m_pushAction = NoPush;
|
||||
};
|
||||
|
||||
struct ModificationInfo
|
||||
{
|
||||
Utils::FilePath rootPath;
|
||||
QSet<QString> modifiedFiles;
|
||||
};
|
||||
|
||||
GitClient();
|
||||
~GitClient();
|
||||
|
||||
@@ -134,6 +140,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,
|
||||
@@ -384,6 +394,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,
|
||||
@@ -401,6 +412,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;
|
||||
|
@@ -157,6 +157,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;
|
||||
@@ -1760,6 +1763,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())
|
||||
@@ -1876,6 +1895,11 @@ void emitRepositoryChanged(const FilePath &r)
|
||||
emit dd->repositoryChanged(r);
|
||||
}
|
||||
|
||||
void emitFileStatusChanged(const FilePath &repository, const QStringList &files)
|
||||
{
|
||||
emit dd->updateFileStatus(repository, files);
|
||||
}
|
||||
|
||||
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) {
|
||||
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,19 @@ void FileNode::setHasError(bool error) const
|
||||
m_hasError = error;
|
||||
}
|
||||
|
||||
bool FileNode::hasModification() const
|
||||
{
|
||||
if (isGenerated())
|
||||
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