forked from qt-creator/qt-creator
Vcs: Add "bulk delete" feature to API
... and use it to improve the user experience when removing several files at once from a project. Fixes: QTCREATORBUG-24385 Change-Id: I8e8c39ee9dc0046f1715a5143a7649fab06e5ad8 Reviewed-by: Orgad Shaneh <orgads@gmail.com>
This commit is contained in:
@@ -157,20 +157,28 @@ QString FileUtils::msgTerminalWithAction()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FileUtils::removeFile(const QString &filePath, bool deleteFromFS)
|
void FileUtils::removeFile(const QString &filePath, bool deleteFromFS)
|
||||||
|
{
|
||||||
|
removeFiles({FilePath::fromString(filePath)}, deleteFromFS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileUtils::removeFiles(const FilePaths &filePaths, bool deleteFromFS)
|
||||||
{
|
{
|
||||||
// remove from version control
|
// remove from version control
|
||||||
VcsManager::promptToDelete(filePath);
|
VcsManager::promptToDelete(filePaths);
|
||||||
|
|
||||||
|
if (!deleteFromFS)
|
||||||
|
return;
|
||||||
|
|
||||||
// remove from file system
|
// remove from file system
|
||||||
if (deleteFromFS) {
|
for (const FilePath &fp : filePaths) {
|
||||||
QFile file(filePath);
|
QFile file(fp.toString());
|
||||||
|
if (!file.exists()) // could have been deleted by vc
|
||||||
if (file.exists()) {
|
continue;
|
||||||
// could have been deleted by vc
|
if (!file.remove()) {
|
||||||
if (!file.remove())
|
MessageManager::write(QCoreApplication::translate(
|
||||||
QMessageBox::warning(ICore::dialogParent(),
|
"Core::Internal",
|
||||||
QApplication::translate("Core::Internal", "Deleting File Failed"),
|
"Failed to remove file \"%1\")1.").
|
||||||
QApplication::translate("Core::Internal", "Could not delete file %1.").arg(filePath));
|
arg(fp.toUserOutput()), MessageManager::ModeSwitch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#include "core_global.h"
|
#include "core_global.h"
|
||||||
|
|
||||||
|
#include <utils/fileutils.h>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
class QWidget;
|
class QWidget;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
@@ -50,6 +52,7 @@ struct CORE_EXPORT FileUtils
|
|||||||
static QString msgTerminalWithAction();
|
static QString msgTerminalWithAction();
|
||||||
// File operations aware of version control and file system case-insensitiveness
|
// File operations aware of version control and file system case-insensitiveness
|
||||||
static void removeFile(const QString &filePath, bool deleteFromFS);
|
static void removeFile(const QString &filePath, bool deleteFromFS);
|
||||||
|
static void removeFiles(const Utils::FilePaths &filePaths, bool deleteFromFS);
|
||||||
static bool renameFile(const QString &from, const QString &to,
|
static bool renameFile(const QString &from, const QString &to,
|
||||||
HandleIncludeGuards handleGuards = HandleIncludeGuards::No);
|
HandleIncludeGuards handleGuards = HandleIncludeGuards::No);
|
||||||
// This method is used to refactor the include guards in the renamed headers
|
// This method is used to refactor the include guards in the renamed headers
|
||||||
|
@@ -335,26 +335,60 @@ QStringList VcsManager::repositories(const IVersionControl *vc)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VcsManager::promptToDelete(const QString &fileName)
|
bool VcsManager::promptToDelete(IVersionControl *versionControl, const QString &fileName)
|
||||||
{
|
{
|
||||||
if (IVersionControl *vc = findVersionControlForDirectory(QFileInfo(fileName).absolutePath()))
|
return promptToDelete(versionControl, {Utils::FilePath::fromString(fileName)}).isEmpty();
|
||||||
return promptToDelete(vc, fileName);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VcsManager::promptToDelete(IVersionControl *vc, const QString &fileName)
|
FilePaths VcsManager::promptToDelete(const FilePaths &filePaths)
|
||||||
{
|
{
|
||||||
QTC_ASSERT(vc, return true);
|
// Categorize files by their parent directory, so we won't call
|
||||||
|
// findVersionControlForDirectory() more often than necessary.
|
||||||
|
QMap<FilePath, FilePaths> filesByParentDir;
|
||||||
|
for (const FilePath &fp : filePaths) {
|
||||||
|
filesByParentDir[FilePath::fromString(QDir::cleanPath(fp.toFileInfo().absolutePath()))]
|
||||||
|
.append(fp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Categorize by version control system.
|
||||||
|
QMap<IVersionControl *, FilePaths> filesByVersionControl;
|
||||||
|
for (auto it = filesByParentDir.cbegin(); it != filesByParentDir.cend(); ++it) {
|
||||||
|
IVersionControl * const vc = findVersionControlForDirectory(it.key().toString());
|
||||||
|
if (vc)
|
||||||
|
filesByVersionControl[vc] << it.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the files.
|
||||||
|
FilePaths failedFiles;
|
||||||
|
for (auto it = filesByVersionControl.cbegin(); it != filesByVersionControl.cend(); ++it)
|
||||||
|
failedFiles << promptToDelete(it.key(), it.value());
|
||||||
|
|
||||||
|
return failedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
FilePaths VcsManager::promptToDelete(IVersionControl *vc, const FilePaths &filePaths)
|
||||||
|
{
|
||||||
|
QTC_ASSERT(vc, return {});
|
||||||
if (!vc->supportsOperation(IVersionControl::DeleteOperation))
|
if (!vc->supportsOperation(IVersionControl::DeleteOperation))
|
||||||
return true;
|
return {};
|
||||||
|
|
||||||
|
const QString fileListForUi = "<ul><li>" + transform(filePaths, [](const FilePath &fp) {
|
||||||
|
return fp.toUserOutput();
|
||||||
|
}).join("</li><li>") + "</li></ul>";
|
||||||
const QString title = tr("Version Control");
|
const QString title = tr("Version Control");
|
||||||
const QString msg = tr("Would you like to remove\n\t%1\nfrom the version control system (%2)?\n"
|
const QString msg = tr("Would you like to remove the following files from from the version control system (%2)?"
|
||||||
"Note: This might remove the local file.").arg(fileName, vc->displayName());
|
"%1Note: This might remove the local file.").arg(fileListForUi, vc->displayName());
|
||||||
const QMessageBox::StandardButton button =
|
const QMessageBox::StandardButton button =
|
||||||
QMessageBox::question(ICore::dialogParent(), title, msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
QMessageBox::question(ICore::dialogParent(), title, msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||||
if (button != QMessageBox::Yes)
|
if (button != QMessageBox::Yes)
|
||||||
return true;
|
return {};
|
||||||
return vc->vcsDelete(fileName);
|
|
||||||
|
FilePaths failedFiles;
|
||||||
|
for (const FilePath &fp : filePaths) {
|
||||||
|
if (!vc->vcsDelete(fp.toString()))
|
||||||
|
failedFiles << fp;
|
||||||
|
}
|
||||||
|
return failedFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString VcsManager::msgAddToVcsTitle()
|
QString VcsManager::msgAddToVcsTitle()
|
||||||
|
@@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
#include "core_global.h"
|
#include "core_global.h"
|
||||||
|
|
||||||
|
#include <utils/fileutils.h>
|
||||||
#include <utils/id.h>
|
#include <utils/id.h>
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
@@ -69,10 +70,12 @@ public:
|
|||||||
|
|
||||||
static QStringList repositories(const IVersionControl *);
|
static QStringList repositories(const IVersionControl *);
|
||||||
|
|
||||||
// Shows a confirmation dialog, whether the file should also be deleted
|
// Shows a confirmation dialog, whether the files should also be deleted
|
||||||
// from revision control. Calls vcsDelete on the file. Returns false
|
// from revision control. Calls vcsDelete on the files. Returns the list
|
||||||
// if a failure occurs
|
// of files that failed.
|
||||||
static bool promptToDelete(const QString &fileName);
|
static Utils::FilePaths promptToDelete(const Utils::FilePaths &filePaths);
|
||||||
|
static Utils::FilePaths promptToDelete(IVersionControl *versionControl,
|
||||||
|
const Utils::FilePaths &filePaths);
|
||||||
static bool promptToDelete(IVersionControl *versionControl, const QString &fileName);
|
static bool promptToDelete(IVersionControl *versionControl, const QString &fileName);
|
||||||
|
|
||||||
// Shows a confirmation dialog, whether the files in the list should be
|
// Shows a confirmation dialog, whether the files in the list should be
|
||||||
|
@@ -161,6 +161,8 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\namespace ProjectExplorer
|
\namespace ProjectExplorer
|
||||||
@@ -3640,7 +3642,7 @@ void ProjectExplorerPluginPrivate::removeFile()
|
|||||||
filesToRemove << siblings;
|
filesToRemove << siblings;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const NodeAndPath &file : filesToRemove) {
|
for (const NodeAndPath &file : qAsConst(filesToRemove)) {
|
||||||
// Nodes can become invalid if the project was re-parsed while the dialog was open
|
// Nodes can become invalid if the project was re-parsed while the dialog was open
|
||||||
if (!ProjectTree::hasNode(file.first)) {
|
if (!ProjectTree::hasNode(file.first)) {
|
||||||
QMessageBox::warning(ICore::dialogParent(), tr("Removing File Failed"),
|
QMessageBox::warning(ICore::dialogParent(), tr("Removing File Failed"),
|
||||||
@@ -3665,12 +3667,17 @@ void ProjectExplorerPluginPrivate::removeFile()
|
|||||||
tr("Could not remove file \"%1\" from project \"%2\".")
|
tr("Could not remove file \"%1\" from project \"%2\".")
|
||||||
.arg(currentFilePath.toUserOutput(), folderNode->managingProject()->displayName()),
|
.arg(currentFilePath.toUserOutput(), folderNode->managingProject()->displayName()),
|
||||||
folderNode->managingProject()->filePath()));
|
folderNode->managingProject()->filePath()));
|
||||||
if (!deleteFile)
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
FileChangeBlocker changeGuard(currentFilePath.toString());
|
|
||||||
Core::FileUtils::removeFile(currentFilePath.toString(), deleteFile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<FileChangeBlocker>> changeGuards;
|
||||||
|
FilePaths pathList;
|
||||||
|
for (const NodeAndPath &file : qAsConst(filesToRemove)) {
|
||||||
|
pathList << file.second;
|
||||||
|
changeGuards.emplace_back(std::make_unique<FileChangeBlocker>(file.second.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Core::FileUtils::removeFiles(pathList, deleteFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectExplorerPluginPrivate::duplicateFile()
|
void ProjectExplorerPluginPrivate::duplicateFile()
|
||||||
|
Reference in New Issue
Block a user