diff --git a/src/plugins/coreplugin/fileutils.cpp b/src/plugins/coreplugin/fileutils.cpp index ee58091a2be..70effcee8f9 100644 --- a/src/plugins/coreplugin/fileutils.cpp +++ b/src/plugins/coreplugin/fileutils.cpp @@ -157,20 +157,28 @@ QString FileUtils::msgTerminalWithAction() } 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 - VcsManager::promptToDelete(filePath); + VcsManager::promptToDelete(filePaths); + + if (!deleteFromFS) + return; // remove from file system - if (deleteFromFS) { - QFile file(filePath); - - if (file.exists()) { - // could have been deleted by vc - if (!file.remove()) - QMessageBox::warning(ICore::dialogParent(), - QApplication::translate("Core::Internal", "Deleting File Failed"), - QApplication::translate("Core::Internal", "Could not delete file %1.").arg(filePath)); + for (const FilePath &fp : filePaths) { + QFile file(fp.toString()); + if (!file.exists()) // could have been deleted by vc + continue; + if (!file.remove()) { + MessageManager::write(QCoreApplication::translate( + "Core::Internal", + "Failed to remove file \"%1\")1."). + arg(fp.toUserOutput()), MessageManager::ModeSwitch); } } } diff --git a/src/plugins/coreplugin/fileutils.h b/src/plugins/coreplugin/fileutils.h index a2dbb5a2e25..5d1ff071778 100644 --- a/src/plugins/coreplugin/fileutils.h +++ b/src/plugins/coreplugin/fileutils.h @@ -27,6 +27,8 @@ #include "core_global.h" +#include + QT_BEGIN_NAMESPACE class QWidget; QT_END_NAMESPACE @@ -50,6 +52,7 @@ struct CORE_EXPORT FileUtils static QString msgTerminalWithAction(); // File operations aware of version control and file system case-insensitiveness 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, HandleIncludeGuards handleGuards = HandleIncludeGuards::No); // This method is used to refactor the include guards in the renamed headers diff --git a/src/plugins/coreplugin/vcsmanager.cpp b/src/plugins/coreplugin/vcsmanager.cpp index 23c1bc792bf..6a56f4dc997 100644 --- a/src/plugins/coreplugin/vcsmanager.cpp +++ b/src/plugins/coreplugin/vcsmanager.cpp @@ -335,26 +335,60 @@ QStringList VcsManager::repositories(const IVersionControl *vc) 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(vc, fileName); - return true; + return promptToDelete(versionControl, {Utils::FilePath::fromString(fileName)}).isEmpty(); } -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 filesByParentDir; + for (const FilePath &fp : filePaths) { + filesByParentDir[FilePath::fromString(QDir::cleanPath(fp.toFileInfo().absolutePath()))] + .append(fp); + } + + // Categorize by version control system. + QMap 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)) - return true; + return {}; + + const QString fileListForUi = "
  • " + transform(filePaths, [](const FilePath &fp) { + return fp.toUserOutput(); + }).join("
  • ") + "
"; 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" - "Note: This might remove the local file.").arg(fileName, vc->displayName()); + const QString msg = tr("Would you like to remove the following files from from the version control system (%2)?" + "%1Note: This might remove the local file.").arg(fileListForUi, vc->displayName()); const QMessageBox::StandardButton button = QMessageBox::question(ICore::dialogParent(), title, msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (button != QMessageBox::Yes) - return true; - return vc->vcsDelete(fileName); + return {}; + + FilePaths failedFiles; + for (const FilePath &fp : filePaths) { + if (!vc->vcsDelete(fp.toString())) + failedFiles << fp; + } + return failedFiles; } QString VcsManager::msgAddToVcsTitle() diff --git a/src/plugins/coreplugin/vcsmanager.h b/src/plugins/coreplugin/vcsmanager.h index 5adaf3f65e3..e663e1ff089 100644 --- a/src/plugins/coreplugin/vcsmanager.h +++ b/src/plugins/coreplugin/vcsmanager.h @@ -27,6 +27,7 @@ #include "core_global.h" +#include #include #include @@ -69,10 +70,12 @@ public: static QStringList repositories(const IVersionControl *); - // Shows a confirmation dialog, whether the file should also be deleted - // from revision control. Calls vcsDelete on the file. Returns false - // if a failure occurs - static bool promptToDelete(const QString &fileName); + // Shows a confirmation dialog, whether the files should also be deleted + // from revision control. Calls vcsDelete on the files. Returns the list + // of files that failed. + 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); // Shows a confirmation dialog, whether the files in the list should be diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp index 8df37279b85..72205a015b8 100644 --- a/src/plugins/projectexplorer/projectexplorer.cpp +++ b/src/plugins/projectexplorer/projectexplorer.cpp @@ -161,6 +161,8 @@ #include #include +#include +#include /*! \namespace ProjectExplorer @@ -3640,7 +3642,7 @@ void ProjectExplorerPluginPrivate::removeFile() 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 if (!ProjectTree::hasNode(file.first)) { QMessageBox::warning(ICore::dialogParent(), tr("Removing File Failed"), @@ -3665,12 +3667,17 @@ void ProjectExplorerPluginPrivate::removeFile() tr("Could not remove file \"%1\" from project \"%2\".") .arg(currentFilePath.toUserOutput(), folderNode->managingProject()->displayName()), folderNode->managingProject()->filePath())); - if (!deleteFile) - continue; } - FileChangeBlocker changeGuard(currentFilePath.toString()); - Core::FileUtils::removeFile(currentFilePath.toString(), deleteFile); } + + std::vector> changeGuards; + FilePaths pathList; + for (const NodeAndPath &file : qAsConst(filesToRemove)) { + pathList << file.second; + changeGuards.emplace_back(std::make_unique(file.second.toString())); + } + + Core::FileUtils::removeFiles(pathList, deleteFile); } void ProjectExplorerPluginPrivate::duplicateFile()