diff --git a/svn/CMakeLists.txt b/svn/CMakeLists.txt --- a/svn/CMakeLists.txt +++ b/svn/CMakeLists.txt @@ -7,9 +7,10 @@ svncommands.cpp svncommitdialog.cpp svnlogdialog.cpp + svncheckoutdialog.cpp ) -ki18n_wrap_ui(fileviewsvnplugin_SRCS svnlogdialog.ui) +ki18n_wrap_ui(fileviewsvnplugin_SRCS svnlogdialog.ui svncheckoutdialog.ui) kconfig_add_kcfg_files(fileviewsvnplugin_SRCS fileviewsvnpluginsettings.kcfgc diff --git a/svn/fileviewsvnplugin.h b/svn/fileviewsvnplugin.h --- a/svn/fileviewsvnplugin.h +++ b/svn/fileviewsvnplugin.h @@ -63,6 +63,7 @@ void removeFiles(); void revertFiles(); void logDialog(); + void checkoutDialog(); void slotOperationCompleted(int exitCode, QProcess::ExitStatus exitStatus); void slotOperationError(); @@ -116,6 +117,7 @@ QAction* m_revertAction; QAction* m_showUpdatesAction; QAction* m_logAction; + QAction* m_checkoutAction; QString m_command; QStringList m_arguments; diff --git a/svn/fileviewsvnplugin.cpp b/svn/fileviewsvnplugin.cpp --- a/svn/fileviewsvnplugin.cpp +++ b/svn/fileviewsvnplugin.cpp @@ -46,6 +46,8 @@ #include "svncommitdialog.h" #include "svnlogdialog.h" +#include "svncheckoutdialog.h" + #include "svncommands.h" K_PLUGIN_FACTORY(FileViewSvnPluginFactory, registerPlugin();) @@ -118,10 +120,15 @@ m_showUpdatesAction, &QAction::setChecked); m_logAction = new QAction(this); - m_logAction->setText(xi18nc("@action:inmenu", "SVN Log...")); + m_logAction->setText(i18nc("@action:inmenu", "SVN Log...")); connect(m_logAction, &QAction::triggered, this, &FileViewSvnPlugin::logDialog); + m_checkoutAction = new QAction(this); + m_checkoutAction->setText(i18nc("@action:inmenu", "SVN Checkout...")); + connect(m_checkoutAction, &QAction::triggered, + this, &FileViewSvnPlugin::checkoutDialog); + connect(&m_process, QOverload::of(&QProcess::finished), this, &FileViewSvnPlugin::slotOperationCompleted); connect(&m_process, &QProcess::errorOccurred, @@ -324,9 +331,14 @@ QList FileViewSvnPlugin::outOfVersionControlActions(const KFileItemList& items) const { - Q_UNUSED(items) + // Only for a single directory. + if (items.count() != 1 || !items.first().isDir()) { + return {}; + } - return {}; + m_contextDir = items.first().localPath(); + + return QList{} << m_checkoutAction; } void FileViewSvnPlugin::updateFiles() @@ -448,6 +460,18 @@ svnLogDialog->show(); } +void FileViewSvnPlugin::checkoutDialog() +{ + SvnCheckoutDialog *svnCheckoutDialog = new SvnCheckoutDialog(m_contextDir); + + connect(svnCheckoutDialog, &SvnCheckoutDialog::infoMessage, this, &FileViewSvnPlugin::infoMessage); + connect(svnCheckoutDialog, &SvnCheckoutDialog::errorMessage, this, &FileViewSvnPlugin::errorMessage); + connect(svnCheckoutDialog, &SvnCheckoutDialog::operationCompletedMessage, this, &FileViewSvnPlugin::operationCompletedMessage); + + svnCheckoutDialog->setAttribute(Qt::WA_DeleteOnClose); + svnCheckoutDialog->show(); +} + void FileViewSvnPlugin::slotOperationCompleted(int exitCode, QProcess::ExitStatus exitStatus) { m_pendingOperation = false; diff --git a/svn/svncheckoutdialog.h b/svn/svncheckoutdialog.h new file mode 100644 --- /dev/null +++ b/svn/svncheckoutdialog.h @@ -0,0 +1,48 @@ +/*************************************************************************** + * Copyright (C) 2020 * + * by Nikolai Krasheninnikov * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef SVNCHECKOUTDIALOG_H +#define SVNCHECKOUTDIALOG_H + +#include + +#include "ui_svncheckoutdialog.h" + +class SvnCheckoutDialog : public QDialog { + Q_OBJECT +public: + SvnCheckoutDialog(const QString& contextDir, QWidget *parent = nullptr); + virtual ~SvnCheckoutDialog() override; + +public slots: + void on_leRepository_textChanged(const QString &text); + void on_pbOk_clicked(); + +signals: + void infoMessage(const QString& msg); + void errorMessage(const QString& msg); + void operationCompletedMessage(const QString& msg); + +private: + Ui::SvnCheckoutDialog m_ui; + QString m_dir; +}; + +#endif // SVNCHECKOUTDIALOG_H diff --git a/svn/svncheckoutdialog.cpp b/svn/svncheckoutdialog.cpp new file mode 100644 --- /dev/null +++ b/svn/svncheckoutdialog.cpp @@ -0,0 +1,125 @@ +/*************************************************************************** + * Copyright (C) 2019-2020 * + * by Nikolai Krasheninnikov * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "svncheckoutdialog.h" + +#include +#include +#include +#include +#include + +#include "svncommands.h" + +namespace{ + +// Helper function: removes trailing slashes. +QString rstrip(const QString &str) +{ + for (int i = str.size() - 1; i >= 0; --i) { + if (str.at(i) != '/') { + return str.left(i + 1); + } + } + + return {}; +} + +// Helper function: check if path is a valid svn repository URL. +// Information about URL prefix at http://svnbook.red-bean.com/en/1.2/svn-book.html#svn.basic.in-action.wc.tbl-1. +bool isValidSvnRepoUrl(const QString &path) +{ + static const QStringList schemes = { "file", "http", "https", "svn", "svn+ssh" }; + + const QUrl url = QUrl::fromUserInput(path); + + return url.isValid() && schemes.contains( url.scheme() ); +} + +} + +SvnCheckoutDialog::SvnCheckoutDialog(const QString& contextDir, QWidget *parent) : + QDialog(parent), + m_dir(contextDir) +{ + m_ui.setupUi(this); + + /* + * Add actions, establish connections. + */ + connect(m_ui.pbCancel, &QPushButton::clicked, this, &QWidget::close); + QAction *pickDirectory = m_ui.leCheckoutDir->addAction(QIcon::fromTheme("folder"), QLineEdit::TrailingPosition); + connect(pickDirectory, &QAction::triggered, this, [this] () { + const QString dir = QFileDialog::getExistingDirectory(this, i18nc("@title:window", "Choose a directory to checkout"), + QString(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + + if (!dir.isEmpty()) { + m_ui.leCheckoutDir->setText(dir); + } + } ); + + /* + * Additional setup. + */ + const QString repoPath = QApplication::clipboard()->text(); + if (isValidSvnRepoUrl(repoPath)) { + m_ui.leRepository->setText(repoPath); + } else { + m_ui.leCheckoutDir->setText(m_dir); + } +} + +SvnCheckoutDialog::~SvnCheckoutDialog() = default; + +void SvnCheckoutDialog::on_leRepository_textChanged(const QString &text) +{ + if (isValidSvnRepoUrl(text)) { + const QString stripped = rstrip(text); + // If URL ends with a 'trunk' this is a branch - lets consider upper folder name as an + // extraction path. So for '.../SomeRepo/trunk/' result would be 'SomeRepo'. + int astart = -1; + if (stripped.endsWith("trunk")) { + astart = -2; + } + const QString suffix = QDir::separator() + stripped.section('/', astart, astart); + + m_ui.leCheckoutDir->setText(m_dir + suffix); + m_ui.pbOk->setEnabled(true); + } else { + m_ui.pbOk->setEnabled(false); + } +} + +void SvnCheckoutDialog::on_pbOk_clicked() +{ + const QString &url = m_ui.leRepository->text(); + const bool omitExternals = m_ui.cbOmitExternals->isChecked(); + const QString &whereto = m_ui.leCheckoutDir->text(); + + emit infoMessage(i18nc("@info:status", "SVN checkout: checkout in process...")); + + if (!SvnCommands::checkoutRepository(url, omitExternals, whereto)) { + emit errorMessage(i18nc("@info:status", "SVN checkout: checkout failed.")); + } else { + emit operationCompletedMessage(i18nc("@info:status", "SVN checkout: checkout successful.")); + } + + close(); +} diff --git a/svn/svncheckoutdialog.ui b/svn/svncheckoutdialog.ui new file mode 100644 --- /dev/null +++ b/svn/svncheckoutdialog.ui @@ -0,0 +1,96 @@ + + + SvnCheckoutDialog + + + + 0 + 0 + 340 + 180 + + + + + 0 + 0 + + + + SVN Checkout + + + + + + URL of repository: + + + + + + + + + + Checkout directory: + + + + + + + + + + Omit externals + + + + + + + + + Qt::Horizontal + + + + 148 + 20 + + + + + + + + false + + + OK + + + + .. + + + + + + + Cancel + + + + .. + + + + + + + + + + diff --git a/svn/svncommands.h b/svn/svncommands.h --- a/svn/svncommands.h +++ b/svn/svncommands.h @@ -161,6 +161,16 @@ * \note This function is really time consuming. */ static QSharedPointer< QVector > getLog(const QString& filePath, uint maxEntries = 255, ulong fromRevision = 0); + + /** + * Check out a working copy of repository \p URL (local URL starts with a 'file://') to a local + * path \p whereto (could be relative ot absolute). + * + * \return True if check out success, false either. + * + * \note This function can be really time consuming. + */ + static bool checkoutRepository(const QString& url, bool ignoreExternals, const QString& whereto); }; #endif // SVNCOMMANDS_H diff --git a/svn/svncommands.cpp b/svn/svncommands.cpp --- a/svn/svncommands.cpp +++ b/svn/svncommands.cpp @@ -403,3 +403,24 @@ return log; } + +bool SvnCommands::checkoutRepository(const QString& url, bool ignoreExternals, const QString& whereto) +{ + QStringList params; + params.append(QStringLiteral("checkout")); + params.append(url); + if (ignoreExternals) { + params.append(QStringLiteral("--ignore-externals")); + } + params.append(whereto); + + QProcess process; + process.start(QLatin1String("svn"), params); + + // Without timeout because it could be expensive time consuming operation. + if (!process.waitForFinished(-1) || process.exitCode() != 0) { + return false; + } else { + return true; + } +}