[svn] Added basic svn commands logging output.

Summary:
{F8324215}
Added dialog for SVN Update, SVN Revert and SVN Commit actions.
This dialog is universal and prints everything from stdout and stderr (stderr prints in red).
Supports for "Cancel" action which sends terminate signal to a running process.

Test Plan:
1) Try "SVN Commit", "SVN Revert" and "SVN Update" actions from plugin popup menu.
2) Try "SVN Revert" action from "SVN Commit" dialog.

Reviewers: #dolphin, #vdg, elvisangelaccio, meven

Reviewed By: #dolphin, elvisangelaccio, meven

Differential Revision: https://phabricator.kde.org/D29788
parent 2f917006
......@@ -8,9 +8,10 @@ set(fileviewsvnplugin_SRCS
svncommitdialog.cpp
svnlogdialog.cpp
svncheckoutdialog.cpp
svnprogressdialog.cpp
)
ki18n_wrap_ui(fileviewsvnplugin_SRCS svnlogdialog.ui svncheckoutdialog.ui)
ki18n_wrap_ui(fileviewsvnplugin_SRCS svnlogdialog.ui svncheckoutdialog.ui svnprogressdialog.ui)
kconfig_add_kcfg_files(fileviewsvnplugin_SRCS
fileviewsvnpluginsettings.kcfgc
......
......@@ -47,6 +47,7 @@
#include "svncommitdialog.h"
#include "svnlogdialog.h"
#include "svncheckoutdialog.h"
#include "svnprogressdialog.h"
#include "svncommands.h"
......@@ -153,7 +154,9 @@ bool FileViewSvnPlugin::beginRetrieval(const QString& directory)
QMutableHashIterator<QString, ItemVersion> it(m_versionInfoHash);
while (it.hasNext()) {
it.next();
if (it.key().startsWith(directory)) {
// 'svn status' return dirs without trailing slash, so without it we can't remove current
// directory from hash.
if ((it.key() + QLatin1Char('/')).startsWith(directory)) {
it.remove();
}
}
......@@ -343,6 +346,9 @@ QList<QAction*> FileViewSvnPlugin::outOfVersionControlActions(const KFileItemLis
void FileViewSvnPlugin::updateFiles()
{
SvnProgressDialog *progressDialog = new SvnProgressDialog(i18nc("@title:window", "SVN Update"), m_contextDir);
progressDialog->connectToProcess(&m_process);
execSvnCommand(QLatin1String("update"), QStringList(),
i18nc("@info:status", "Updating SVN repository..."),
i18nc("@info:status", "Update of SVN repository failed."),
......@@ -434,13 +440,24 @@ void FileViewSvnPlugin::removeFiles()
void FileViewSvnPlugin::revertFiles()
{
if (m_contextDir.isEmpty() && m_contextItems.empty()) {
return;
}
QStringList arguments;
QString root;
// If we are reverting a directory let's revert everything in it.
if (!m_contextDir.isEmpty()) {
arguments << QLatin1String("--depth") << QLatin1String("infinity");
root = m_contextDir;
} else {
root = SvnCommands::localRoot( m_contextItems.last().localPath() );
}
SvnProgressDialog *progressDialog = new SvnProgressDialog(i18nc("@title:window", "SVN Revert"), root);
progressDialog->connectToProcess(&m_process);
execSvnCommand(QStringLiteral("revert"), arguments,
i18nc("@info:status", "Reverting files from SVN repository..."),
i18nc("@info:status", "Reverting of files from SVN repository failed."),
......@@ -507,11 +524,18 @@ void FileViewSvnPlugin::slotShowUpdatesToggled(bool checked)
void FileViewSvnPlugin::revertFiles(const QStringList& filesPath)
{
if (filesPath.empty()) {
return;
}
for (const auto &i : qAsConst(filesPath)) {
m_contextItems.append( QUrl::fromLocalFile(i) );
}
m_contextDir.clear();
SvnProgressDialog *progressDialog = new SvnProgressDialog(i18nc("@title:window", "SVN Revert"), SvnCommands::localRoot(filesPath.first()));
progressDialog->connectToProcess(&m_process);
execSvnCommand(QLatin1String("revert"), QStringList() << filesPath,
i18nc("@info:status", "Reverting changes to file..."),
i18nc("@info:status", "Revert file failed."),
......@@ -592,6 +616,10 @@ void FileViewSvnPlugin::addFiles(const QStringList& filesPath)
void FileViewSvnPlugin::commitFiles(const QStringList& context, const QString& msg)
{
if (context.empty()) {
return;
}
// Write the commit description into a temporary file, so
// that it can be read by the command "svn commit -F". The temporary
// file must stay alive until slotOperationCompleted() is invoked and will
......@@ -615,6 +643,9 @@ void FileViewSvnPlugin::commitFiles(const QStringList& context, const QString& m
m_contextDir.clear();
m_contextItems.clear();
SvnProgressDialog *progressDialog = new SvnProgressDialog(i18nc("@title:window", "SVN Commit"), SvnCommands::localRoot(context.first()));
progressDialog->connectToProcess(&m_process);
execSvnCommand(QLatin1String("commit"), arguments,
i18nc("@info:status", "Committing SVN changes..."),
i18nc("@info:status", "Commit of SVN changes failed."),
......
......@@ -190,6 +190,35 @@ QString SvnCommands::remoteRelativeUrl(const QString& filePath)
}
}
QString SvnCommands::localRoot(const QString& filePath)
{
QProcess process;
process.start(
QLatin1String("svn"),
QStringList {
QStringLiteral("info"),
QStringLiteral("--show-item"),
QStringLiteral("wc-root"),
filePath
}
);
if (!process.waitForFinished() || process.exitCode() != 0) {
return 0;
}
QTextStream stream(&process);
QString wcroot;
stream >> wcroot;
if (stream.status() == QTextStream::Ok) {
return wcroot;
} else {
return QString();
}
}
bool SvnCommands::updateToRevision(const QString& filePath, ulong revision)
{
QProcess process;
......@@ -256,6 +285,33 @@ bool SvnCommands::revertToRevision(const QString& filePath, ulong revision)
return true;
}
bool SvnCommands::cleanup(const QString& dir, bool removeUnversioned, bool removeIgnored, bool includeExternals)
{
QStringList arguments;
arguments << QStringLiteral("cleanup") << dir;
if (removeUnversioned) {
arguments << QStringLiteral("--remove-unversioned");
}
if (removeIgnored) {
arguments << QStringLiteral("--remove-ignored");
}
if (includeExternals) {
arguments << QStringLiteral("--include-externals");
}
QProcess process;
process.start(
QLatin1String("svn"),
arguments
);
if (!process.waitForFinished() || process.exitCode() != 0) {
return false;
} else {
return true;
}
}
bool SvnCommands::exportFile(const QUrl& path, ulong rev, QFileDevice *file)
{
if (file == nullptr || !path.isValid()) {
......
......@@ -112,17 +112,26 @@ public:
static QString remoteRelativeUrl(const QString& filePath);
/**
* Updates selected \p filePath to revision \p revision. \p filePath could be a sigle file or a
* From file \p filePath returns full working copy root path this file contains.
*
* \return Full local working copy root path, empty QString in case of error.
*
* \note This function uses only local SVN data without connection to a remote so it's fast.
*/
static QString localRoot(const QString& filePath);
/**
* Updates selected \p filePath to revision \p revision. \p filePath could be a single file or a
* directory. It also could be an absolute or relative.
*
* \return True on success, false either.
*
* \note This function uses only local SVN data without connection to a remote so it's fast.
* \note This function can be really time consuming.
*/
static bool updateToRevision(const QString& filePath, ulong revision);
/**
* Discards all local changes in a \p filePath. \p filePath could be a sigle file or a directory.
* Discards all local changes in a \p filePath. \p filePath could be a single file or a directory.
* It also could be an absolute or relative.
*
* \return True on success, false either.
......@@ -132,13 +141,22 @@ public:
static bool revertLocalChanges(const QString& filePath);
/**
* Reverts selected \p filePath to revision \p revision. \p filePath could be a sigle file or a
* Reverts selected \p filePath to revision \p revision. \p filePath could be a single file or a
* directory. It also could be an absolute or relative.
*
* \return True on success, false either.
*/
static bool revertToRevision(const QString& filePath, ulong revision);
/**
* Runs 'svn cleanup' on a \p dir to remove write locks, resume unfinished operations, etc. Its
* restores directory state if Subversion client has crushed.
* Also this command could be used to remove unversioned or ignored files.
*
* \return True on success, false either.
*/
static bool cleanup(const QString& dir, bool removeUnversioned = false, bool removeIgnored = false, bool includeExternals = false);
/**
* Export URL \p path at revision \p rev to a file \p file. URL could be a remote URL to a file
* or directory or path to a local file or directory (both relative or absolute). File should
......
/***************************************************************************
* Copyright (C) 2019-2020 *
* by Nikolai Krasheninnikov <nkrasheninnikov@yandex.ru> *
* *
* 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 "svnprogressdialog.h"
#include <QProcess>
#include <QDebug>
#include "svncommands.h"
SvnProgressDialog::SvnProgressDialog(const QString& title, const QString& workingDir, QWidget *parent) :
QDialog(parent),
m_svnTerminated(false),
m_workingDir(workingDir)
{
m_ui.setupUi(this);
/*
* Add actions, establish connections.
*/
QObject::connect(m_ui.buttonOk, &QPushButton::clicked, this, &QDialog::close);
/*
* Additional setup.
*/
setAttribute(Qt::WA_DeleteOnClose);
setWindowTitle(title);
show();
activateWindow();
}
SvnProgressDialog::~SvnProgressDialog()
{
disconnectFromProcess();
}
void SvnProgressDialog::connectToProcess(QProcess *process)
{
disconnectFromProcess();
m_svnTerminated = false;
m_conCancel = connect(m_ui.buttonCancel, &QPushButton::clicked, [this, process] () {
process->terminate();
m_svnTerminated = true;
} );
m_conCompeted = connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &SvnProgressDialog::operationCompeleted);
m_conProcessError = connect(process, &QProcess::errorOccurred, [this, process] (QProcess::ProcessError) {
const QString commandLine = process->program() + process->arguments().join(' ');
appendErrorText(i18nc("@info:status", "Error starting: %1", commandLine));
operationCompeleted();
} );
m_conStdOut = connect(process, &QProcess::readyReadStandardOutput, [this, process] () {
appendInfoText( process->readAllStandardOutput() );
} );
m_conStrErr = connect(process, &QProcess::readyReadStandardError, [this, process] () {
appendErrorText( process->readAllStandardError() );
} );
}
void SvnProgressDialog::disconnectFromProcess()
{
QObject::disconnect(m_conCancel);
QObject::disconnect(m_conCompeted);
QObject::disconnect(m_conProcessError);
QObject::disconnect(m_conStdOut);
QObject::disconnect(m_conStrErr);
}
void SvnProgressDialog::appendInfoText(const QString& text)
{
const QTextCursor pos = m_ui.texteditMessage->textCursor();
m_ui.texteditMessage->moveCursor(QTextCursor::End);
m_ui.texteditMessage->insertPlainText(text);
m_ui.texteditMessage->setTextCursor(pos);
}
void SvnProgressDialog::appendErrorText(const QString& text)
{
static const QString htmlBegin = "<font color=\"Red\">";
static const QString htmlEnd = "</font><br>";
QString message = QString(text).replace('\n', QLatin1String("<br>"));
// Remove last <br> as it will be in htmlEnd.
if (message.endsWith(QLatin1String("<br>"))) {
message.chop(4);
}
m_ui.texteditMessage->appendHtml(htmlBegin + message + htmlEnd);
}
void SvnProgressDialog::operationCompeleted()
{
disconnectFromProcess();
if (m_svnTerminated && !m_workingDir.isEmpty()) {
if (!SvnCommands::cleanup(m_workingDir)) {
qDebug() << QString("'svn cleanup' failed for %1").arg(m_workingDir);
}
m_svnTerminated = false;
}
m_ui.buttonOk->setEnabled(true);
m_ui.buttonCancel->setEnabled(false);
}
void SvnProgressDialog::reject()
{
if (m_ui.buttonOk->isEnabled()) {
QDialog::reject();
} else {
emit m_ui.buttonCancel->clicked();
}
}
/***************************************************************************
* Copyright (C) 2019-2020 *
* by Nikolai Krasheninnikov <nkrasheninnikov@yandex.ru> *
* *
* 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 SVNPROGRESSDIALOG_H
#define SVNPROGRESSDIALOG_H
#include <QDialog>
#include "ui_svnprogressdialog.h"
class QProcess;
/**
* \brief Dialog for showing SVN operation process.
*
* This dialog connects to the Subversion process (by \p connectToProcess()) and shows its output.
* User has possibility to terminate the process by pressing cancel button. Normally do not need to
* call \p disconnectFromProcess() as it calls automaticaly on connected process finished() signal.
*
* \note This class can call 'svn cleanup' on a Subversion process dir in case of terminating it if
* a working directory were passed to the constructor.
*/
class SvnProgressDialog : public QDialog {
Q_OBJECT
public:
/**
* \param[in] title Dialog title.
* \param[in] workingDir Directory to call 'svn cleanup' on. Empty for no cleanup.
* \param[in,out] parent Parent widget.
*/
SvnProgressDialog(const QString& title, const QString& workingDir = QString(), QWidget *parent = nullptr);
virtual ~SvnProgressDialog() override;
/**
* Connects to the process signals, stdout and stderr.
*/
void connectToProcess(QProcess *process);
/**
* Disconnects from previously connected process, nothing happens either. This function is
* automaticaly called on connected process finished() signal.
*/
void disconnectFromProcess();
public slots:
void appendInfoText(const QString& text);
void appendErrorText(const QString& text);
void operationCompeleted();
virtual void reject() override;
private:
Ui::SvnProgressDialog m_ui;
QMetaObject::Connection m_conCancel;
QMetaObject::Connection m_conCompeted;
QMetaObject::Connection m_conProcessError;
QMetaObject::Connection m_conStdOut;
QMetaObject::Connection m_conStrErr;
bool m_svnTerminated;
const QString m_workingDir;
};
#endif // SVNPROGRESSDIALOG_H
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SvnProgressDialog</class>
<widget class="QWidget" name="SvnProgressDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>521</width>
<height>409</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string/>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="3">
<widget class="QPlainTextEdit" name="texteditMessage">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="buttonCancel">
<property name="text">
<string>Cancel</string>
</property>
<property name="icon">
<iconset theme="dialog-cancel">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="buttonOk">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>OK</string>
</property>
<property name="icon">
<iconset theme="dialog-ok">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="autoDefault">
<bool>true</bool>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>328</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment