# # # patch "NEWS" # from [fb27ac52cbbbfabb0c4559dc4dbd2d6e8135ff16] # to [e35a5ba01064e6bca95e4f3ef6b60ceea4d64f0b] # # patch "src/util/Platform.cpp" # from [84cc2038c53f656294a339d737122f7694ffd0c2] # to [715b155afa478dddaa4d6936e363ef56832d10de] # # patch "src/util/Platform.h" # from [d7189f5bcc1063d1133c0e8fe64de81f81b39c9d] # to [a92915f23e26aafae4bc9919629b71cd0b36c3a5] # # patch "src/util/Settings.cpp" # from [003ba7d2afcc6d924286aefdaa7c3619ef0d66b9] # to [780e06e95af4526a874f16c1c19f605c7c5f726e] # # patch "src/view/dialogs/RevisionManifest.cpp" # from [c7b0722f1a40388a22c2ca0d71e412ead65e9395] # to [85c59d07bc72fa7462d641e8a4e9fed2ea86d9e1] # # patch "src/view/mainwindows/WorkspaceWindow.cpp" # from [9971a3d11bd369c22873bff455f6228fb3a1d45c] # to [fc83741e54b723b37ef5127f3f15ebfeba45fc07] # # patch "src/view/mainwindows/WorkspaceWindow.h" # from [1ca261b3d08d4593f25670bbbfbf389c0ad16bc5] # to [d707e8956f801c8e6f9b2a6fe8eceb78b61de885] # # patch "src/view/widgets/InventoryView.cpp" # from [4c9c0709c58278d302183d8e6d67cd1b5f64f694] # to [096d37b7f13cfb43d92d324f2f59082e072292a3] # # patch "src/view/widgets/InventoryView.h" # from [ac58d3b7ec668ea50d13c8b2260d82b8b7e514fb] # to [dafe99dceceea43ffa850cd3a891eb29c916551e] # # patch "src/vocab.h" # from [0f05c39e3640906a00c77880e8dc76255d3e3a90] # to [5694058c4d9c3ac022cac2459eac5b61ff6e2fca] # ============================================================ --- NEWS fb27ac52cbbbfabb0c4559dc4dbd2d6e8135ff16 +++ NEWS e35a5ba01064e6bca95e4f3ef6b60ceea4d64f0b @@ -6,6 +6,7 @@ is part of another source tree - new: actually add, drop, revert, ignore, unignore items in the workspace view and add two new options to create new versioned files and directories + - new: unknown and ignored items in a workspace can be deleted from disk - new: change the passphrase of a key - new: sort and filter the key list - new: remember the entered passphrase of a key for later usage, f.e. ============================================================ --- src/util/Platform.cpp 84cc2038c53f656294a339d737122f7694ffd0c2 +++ src/util/Platform.cpp 715b155afa478dddaa4d6936e363ef56832d10de @@ -22,7 +22,6 @@ #include #include -#include #include #include @@ -42,7 +41,7 @@ using namespace std; // It opens a file with the associated program on the specific platform // Original author: Chris Thompson // -bool Platform::openFile(const QString & filename, bool isSymlink /* = false */) +void Platform::openFile(const QString & filename, bool isSymlink /* = false */) { // if this looks like a file, and not like an URL or something else if (filename.indexOf(QRegExp("^\\w+:\\/\\/")) == -1) @@ -50,57 +49,22 @@ bool Platform::openFile(const QString & QFileInfo fileInfo(filename); if (!fileInfo.exists()) { - QMessageBox::critical( - NULL, - QObject::tr("Error"), - QObject::tr("The file you're trying to open does not exist."), - QMessageBox::Ok, 0, 0 + throw GuitoneException( + QObject::tr("The file does not exist.") ); - return false; } if (fileInfo.isSymLink()) { if (isSymlink) { - QMessageBox::critical( - NULL, - QObject::tr("Error"), - QObject::tr("No support for nested symlinks - " - "abort opening of %1").arg(filename), - QMessageBox::Ok, 0, 0 + throw GuitoneException( + QObject::tr("No support for nested symlinks.") ); - return false; } - return openFile(fileInfo.symLinkTarget(), true); + openFile(fileInfo.symLinkTarget(), true); + return; } - - if (Settings::getBool("AskFileOpen")) - { - QString suffix = fileInfo.completeSuffix(); - QStringList harmfulSuffixes = - Settings::getString("AskFileOpenExt", ASK_OPEN_FILE_EXT) - .split(",", QString::SkipEmptyParts); - - if (!fileInfo.isDir() && - (fileInfo.isExecutable() || harmfulSuffixes.contains(suffix))) - { - QMessageBox::StandardButton btn = QMessageBox::question( - NULL, - QObject::tr("Attention"), - QObject::tr("The file '%1' you're trying to open is either " - "executable or included in the list of " - "precarious file formats - do you really " - "want to continue?").arg(filename), - QMessageBox::Yes | QMessageBox::No - ); - - if (btn == QMessageBox::No) - { - return false; - } - } - } } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); @@ -129,7 +93,9 @@ bool Platform::openFile(const QString & } else { - C("Neither kfmclient nor gnome-open found"); + throw GuitoneException( + QObject::tr("Neither kfmclient nor gnome-open found.") + ); } #endif @@ -172,17 +138,11 @@ bool Platform::openFile(const QString & if (retval != 0) { - QMessageBox::critical( - NULL, - QObject::tr("Error"), - QObject::tr("Unable to open files on your platform - please " - "contact the author about this problem."), - QMessageBox::Ok, 0, 0 - ); - return false; + throw GuitoneException(QObject::tr( + "Unable to open files on your platform - please " + "contact the author about this problem." + )); } - - return true; } // @@ -237,14 +197,14 @@ QDir Platform::safeMakePath(const QStrin if (!fileInfo.isDir()) { throw GuitoneException( - QObject::tr("Path exists, but is not a directory") + QObject::tr("Path exists, but is not a directory.") ); } if (!fileInfo.isWritable()) { throw GuitoneException( - QObject::tr("Directory exists, but is not writable") + QObject::tr("Directory exists, but is not writable.") ); } @@ -254,7 +214,7 @@ QDir Platform::safeMakePath(const QStrin if (dir.entryList(filters).size() > 0) { throw GuitoneException( - QObject::tr("Directory is not empty") + QObject::tr("Directory is not empty.") ); } return dir; @@ -263,7 +223,7 @@ QDir Platform::safeMakePath(const QStrin if (path.indexOf(QRegExp("(^|[\\/])\\.\\.([\\/]|$)")) != -1) { throw GuitoneException( - QObject::tr("Relative paths are not supported") + QObject::tr("Relative paths are not supported.") ); } @@ -276,7 +236,7 @@ QDir Platform::safeMakePath(const QStrin if (path.left(2) == "\\\\") { throw GuitoneException( - QObject::tr("UNC paths are not supported") + QObject::tr("UNC paths are not supported.") ); } @@ -296,7 +256,7 @@ QDir Platform::safeMakePath(const QStrin if (!driveFound) { throw GuitoneException( - QObject::tr("Drive %1 does not exist").arg(prefixPath) + QObject::tr("Drive %1 does not exist.").arg(prefixPath) ); } } @@ -312,10 +272,81 @@ QDir Platform::safeMakePath(const QStrin if (!dir.mkpath(actualPath)) { throw GuitoneException( - QObject::tr("Path could not be created") + QObject::tr("Path could not be created.") ); } return dir; } +void Platform::removeRecursively(const QString & path) +{ + QFileInfo fileInfo(path); + if (!fileInfo.exists()) + { + throw GuitoneException( + QObject::tr("Path '%1' does not exist.").arg(path) + ); + } + + if (!fileInfo.isWritable()) + { + throw GuitoneException( + QObject::tr("Path '%1' is not writable.").arg(path) + ); + } + +#ifdef Q_WS_WIN + if (path.left(2) == "\\\\") + { + throw GuitoneException( + QObject::tr("UNC paths are not supported.") + ); + } + + // sanity + if (path.size() == 3 && (path.mid(1, 2) == ":\\" || path.mid(1, 2) == ":/")) + { + throw GuitoneException( + QObject::tr("Cannot remove drive '%1'.").arg(path) + ); + } +#else + if (path.size() == 1 && path.left(1) == "/") + { + throw GuitoneException( + QObject::tr("Cannot remove root directory.") + ); + } +#endif + + if (!fileInfo.isDir()) + { + if (!QFile::remove(path)) + { + throw GuitoneException( + QObject::tr("Cannot remove file '%1'.").arg(path) + ); + } + return; + } + + QDir dir(path); + QStringList entries = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot | + QDir::Hidden | QDir::System); + foreach (QString entry, entries) + { + Platform::removeRecursively(dir.filePath(entry)); + } + + QString dirName = dir.dirName(); + I(dir.cdUp()); + + if (!dir.rmdir(dirName)) + { + throw GuitoneException( + QObject::tr("Cannot remove directory '%2'.").arg(path) + ); + } +} + ============================================================ --- src/util/Platform.h d7189f5bcc1063d1133c0e8fe64de81f81b39c9d +++ src/util/Platform.h a92915f23e26aafae4bc9919629b71cd0b36c3a5 @@ -29,7 +29,7 @@ public: * Opens a file or URL with the default registered handler * The URL is assumed to be valid. No security checks are performed. */ - static bool openFile(const QString & filename, bool isSymlink = false); + static void openFile(const QString & filename, bool isSymlink = false); /** * Returns the name of the currently logged in user of the system */ @@ -39,6 +39,10 @@ public: * the path already exists, ensures that its valid and writable */ static QDir safeMakePath(const QString & path); + /** + * Executes rm -rf on the path + */ + static void removeRecursively(const QString & path); }; #endif ============================================================ --- src/util/Settings.cpp 003ba7d2afcc6d924286aefdaa7c3619ef0d66b9 +++ src/util/Settings.cpp 780e06e95af4526a874f16c1c19f605c7c5f726e @@ -40,6 +40,13 @@ Settings::Settings() : QSettings() { stringDefaults.insert("DiffColorAddedLines", "#90EE90"); stringDefaults.insert("DiffColorRemovedLines", "#FFCC99"); + stringDefaults.insert("AskFileOpenExt", + "exe,com,bat,pif,hta,js,jse,inf,lnk," // Windows + "scr,wsc,wsf,wsh,vb,vbe,vbs,vbscript," // Windows, continued + "sh,bsh,zsh,csh,ksh,py,pl,php,php3," // Unices + "php4,rb,ruby,awk,cgi,jar," // Unices, continued + "app,pkg,dmg,scpt" // Mac OS X + ); boolDefaults.insert("ConsoleLogEnabled", true); boolDefaults.insert("FileLogEnabled", false); ============================================================ --- src/view/dialogs/RevisionManifest.cpp c7b0722f1a40388a22c2ca0d71e412ead65e9395 +++ src/view/dialogs/RevisionManifest.cpp 85c59d07bc72fa7462d641e8a4e9fed2ea86d9e1 @@ -20,6 +20,7 @@ #include "Platform.h" #include "MonotoneUtil.h" #include "FileExporter.h" +#include "Settings.h" #include #include @@ -119,16 +120,50 @@ void RevisionManifest::openFile(const QM file.write(task->getOutput()); file.close(); - // if the action was successful, block execution (i.e. deleting of the - // temporary file) until the user tells us to continue - if (Platform::openFile(tempPath)) + if (Settings::getBool("AskFileOpen")) { + QFileInfo fileInfo(tempPath); + QString suffix = fileInfo.completeSuffix(); + QStringList harmfulSuffixes = + Settings::getString("AskFileOpenExt").split(",", QString::SkipEmptyParts); + + if (harmfulSuffixes.contains(suffix)) + { + QMessageBox::StandardButton btn = QMessageBox::question( + NULL, + QObject::tr("Attention"), + QObject::tr("The file '%1' you're trying to open is included " + "in the list of precarious file formats - do you " + "really want to continue?").arg(tempPath), + QMessageBox::Yes | QMessageBox::No + ); + + if (btn == QMessageBox::No) + return; + } + } + + try + { + Platform::openFile(tempPath); + + // if the action was successful, block execution (i.e. deleting of the + // temporary file) until the user tells us to continue QMessageBox::information( this, tr("Information"), tr("Please close this message to remove the temporary file.") ); } + catch (GuitoneException & e) + { + QMessageBox::critical( + this, + tr("Error"), + tr("Could not open '%1': %2").arg(tempPath).arg(e.what()), + QMessageBox::Ok, 0, 0 + ); + } file.remove(); } ============================================================ --- src/view/mainwindows/WorkspaceWindow.cpp 9971a3d11bd369c22873bff455f6228fb3a1d45c +++ src/view/mainwindows/WorkspaceWindow.cpp fc83741e54b723b37ef5127f3f15ebfeba45fc07 @@ -172,6 +172,16 @@ void WorkspaceWindow::setup() this, SLOT(openFile(const QString &)) ); + connect( + listView, SIGNAL(deleteLocalPath(const QString &)), + this, SLOT(deleteLocalPath(const QString &)) + ); + + connect( + treeView, SIGNAL(deleteLocalPath(const QString &)), + this, SLOT(deleteLocalPath(const QString &)) + ); + // the tree view shows only directories, therefor file diff / file history // doesn't make sense there connect( @@ -417,9 +427,82 @@ void WorkspaceWindow::openFile(const QSt void WorkspaceWindow::openFile(const QString & filePath) { - Platform::openFile(workspacePath + "/" + filePath); + QString completePath = workspacePath + "/" + filePath; + QFileInfo fileInfo(completePath); + + if (!fileInfo.exists()) + { + W(QString("cannot open %1, file does not exist").arg(completePath)); + return; + } + + if (Settings::getBool("AskFileOpen")) + { + QString suffix = fileInfo.completeSuffix(); + QStringList harmfulSuffixes = + Settings::getString("AskFileOpenExt").split(",", QString::SkipEmptyParts); + + if (!fileInfo.isDir() && (fileInfo.isExecutable() || harmfulSuffixes.contains(suffix))) + { + QMessageBox::StandardButton btn = QMessageBox::question( + NULL, + QObject::tr("Attention"), + QObject::tr("The file '%1' you're trying to open is either " + "executable or included in the list of " + "precarious file formats - do you really " + "want to continue?").arg(completePath), + QMessageBox::Yes | QMessageBox::No + ); + + if (btn == QMessageBox::No) + return; + } + } + + try + { + Platform::openFile(completePath); + } + catch (GuitoneException & e) + { + QMessageBox::critical( + this, + tr("Error"), + tr("Could not open '%1': %2").arg(completePath).arg(e.what()), + QMessageBox::Ok, 0, 0 + ); + } } +void WorkspaceWindow::deleteLocalPath(const QString & path) +{ + QString completePath = workspacePath + "/" + path; + + QMessageBox::StandardButton btn = QMessageBox::question( + this, + tr("Delete confirmation"), + tr("Do you really want to delete '%1'? " + "This action cannot be undone.").arg(completePath), + QMessageBox::Yes | QMessageBox::No + ); + + if (btn == QMessageBox::No) return; + + try + { + Platform::removeRecursively(completePath); + } + catch (GuitoneException & e) + { + QMessageBox::critical( + this, + tr("Error"), + tr("Could not remove '%1': %2").arg(completePath).arg(e.what()), + QMessageBox::Ok, 0, 0 + ); + } +} + void WorkspaceWindow::maybeReadNodeInfo(const QModelIndexList & indexes) { attrModel->revert(); ============================================================ --- src/view/mainwindows/WorkspaceWindow.h 1ca261b3d08d4593f25670bbbfbf389c0ad16bc5 +++ src/view/mainwindows/WorkspaceWindow.h d707e8956f801c8e6f9b2a6fe8eceb78b61de885 @@ -74,6 +74,7 @@ private slots: void invalidWorkspaceFormat(); void workspaceCommandError(const QString &); void openFile(const QString &); + void deleteLocalPath(const QString &); }; #endif ============================================================ --- src/view/widgets/InventoryView.cpp 4c9c0709c58278d302183d8e6d67cd1b5f64f694 +++ src/view/widgets/InventoryView.cpp 096d37b7f13cfb43d92d324f2f59082e072292a3 @@ -167,6 +167,10 @@ void InventoryView::createAndConnectCont actNewDirectory->setStatusTip(tr("Creates a new versioned directory")); connect(actNewDirectory, SIGNAL(triggered()), this, SLOT(slotNewDirectory())); + actDelete = new QAction(tr("Delete"), this); + actDelete->setStatusTip(tr("Deletes the file from the filesystem")); + connect(actDelete, SIGNAL(triggered()), this, SLOT(slotDelete())); + actAddMultiple = new QAction(tr("Add %1 items"), this); actAddMultiple->setStatusTip(tr("Add multiple items")); connect(actAddMultiple, SIGNAL(triggered()), this, SLOT(slotAdd())); @@ -209,6 +213,7 @@ InventoryView::~InventoryView() delete actRefresh; delete actNewFile; delete actNewDirectory; + delete actDelete; delete actAddMultiple; delete actRemoveMultiple; delete actCommitMultiple; @@ -365,11 +370,15 @@ void InventoryView::slotContextMenuReque { menu.addAction(actAdd); menu.addAction(actIgnore); + menu.addSeparator(); + menu.addAction(actDelete); } if (invitem->hasStatus(InventoryItem::Ignored)) { menu.addAction(actUnignore); + menu.addSeparator(); + menu.addAction(actDelete); } if (invitem->hasChangedRecursive()) @@ -1052,6 +1061,19 @@ void InventoryView::slotNewDirectory() emit newDirectory(newPath); } +void InventoryView::slotDelete() +{ + QSet items = getSelectedItems(); + if (items.size() == 0) return; + + const InventoryItem * invitem = dynamic_cast(*items.begin()); + + if (!invitem) + return; + + emit deleteLocalPath(invitem->getPath()); +} + QSet InventoryView::getSelectedItems() const { QItemSelectionModel * selectionModel = this->selectionModel(); ============================================================ --- src/view/widgets/InventoryView.h ac58d3b7ec668ea50d13c8b2260d82b8b7e514fb +++ src/view/widgets/InventoryView.h dafe99dceceea43ffa850cd3a891eb29c916551e @@ -56,6 +56,7 @@ signals: void unignorePaths(const QStringList &); void newFile(const QString &); void newDirectory(const QString &); + void deleteLocalPath(const QString &); private: enum DefaultAction { None, Chdir, Open, FileDiff, Commit }; @@ -87,6 +88,7 @@ private: QAction * actRefresh; QAction * actNewFile; QAction * actNewDirectory; + QAction * actDelete; QAction * actAddMultiple; QAction * actRemoveMultiple; @@ -125,6 +127,7 @@ private slots: void slotRefresh(); void slotNewFile(); void slotNewDirectory(); + void slotDelete(); }; #endif ============================================================ --- src/vocab.h 0f05c39e3640906a00c77880e8dc76255d3e3a90 +++ src/vocab.h 5694058c4d9c3ac022cac2459eac5b61ff6e2fca @@ -174,13 +174,5 @@ public: const char * what() const throw() { return message.toStdString().c_str(); } }; -// -// global constants -// -const QString ASK_OPEN_FILE_EXT = - "exe,com,bat,pif,hta,js,jse,inf,lnk,scr,wsc,wsf,wsh,vb,vbe,vbs,vbscript," // Windows - "sh,bsh,zsh,csh,ksh,py,pl,php,php3,php4,rb,ruby,awk,cgi,jar," // Unix/Linux/Mac - "app,pkg,dmg,scpt"; // Mac OS X - #endif