Commit d7de9444 authored by Nikolai Krasheninnikov's avatar Nikolai Krasheninnikov Committed by Elvis Angelaccio

SVN: added SVN Log dialog

Summary:
Added SVN Log dialog. Dialog looks and behaves similar to a TortoiseSVN one.
Dialog supports:
- update repo to specified revision;
- revert repo to specified revision;
- revert file to a specified revision;
- show changes against previois commit;
- show changes against working copy.
Everything is done by the context menu.

{F8181378}

Test Plan: Run SVN Log dialog and check update works, revert works, revert file works, show changes and show changes against working copy works.

Reviewers: #vdg, meven, elvisangelaccio

Reviewed By: elvisangelaccio

Subscribers: yurchor, anthonyfieroni

Differential Revision: https://phabricator.kde.org/D28102
parent 865e901a
......@@ -6,8 +6,11 @@ set(fileviewsvnplugin_SRCS
fileviewsvnplugin.cpp
svncommands.cpp
svncommitdialog.cpp
svnlogdialog.cpp
)
ki18n_wrap_ui(fileviewsvnplugin_SRCS svnlogdialog.ui)
kconfig_add_kcfg_files(fileviewsvnplugin_SRCS
fileviewsvnpluginsettings.kcfgc
)
......
......@@ -45,6 +45,7 @@
#include <QHeaderView>
#include "svncommitdialog.h"
#include "svnlogdialog.h"
#include "svncommands.h"
K_PLUGIN_FACTORY(FileViewSvnPluginFactory, registerPlugin<FileViewSvnPlugin>();)
......@@ -59,6 +60,7 @@ FileViewSvnPlugin::FileViewSvnPlugin(QObject* parent, const QList<QVariant>& arg
m_addAction(0),
m_removeAction(0),
m_showUpdatesAction(0),
m_logAction(nullptr),
m_command(),
m_arguments(),
m_errorMsg(),
......@@ -115,6 +117,11 @@ FileViewSvnPlugin::FileViewSvnPlugin(QObject* parent, const QList<QVariant>& arg
connect(this, SIGNAL(setShowUpdatesChecked(bool)),
m_showUpdatesAction, SLOT(setChecked(bool)));
m_logAction = new QAction(this);
m_logAction->setText(xi18nc("@action:inmenu", "SVN Log..."));
connect(m_logAction, &QAction::triggered,
this, &FileViewSvnPlugin::logDialog);
connect(&m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, &FileViewSvnPlugin::slotOperationCompleted);
connect(&m_process, &QProcess::errorOccurred,
......@@ -370,7 +377,7 @@ void FileViewSvnPlugin::commitDialog()
connect(this, &FileViewSvnPlugin::versionInfoUpdated, svnCommitDialog, &SvnCommitDialog::refreshChangesList);
connect(svnCommitDialog, &SvnCommitDialog::revertFiles, this, QOverload<const QStringList&>::of(&FileViewSvnPlugin::revertFiles));
connect(svnCommitDialog, &SvnCommitDialog::diffFile, this, &FileViewSvnPlugin::diffFile);
connect(svnCommitDialog, &SvnCommitDialog::diffFile, this, QOverload<const QString&>::of(&FileViewSvnPlugin::diffFile));
connect(svnCommitDialog, &SvnCommitDialog::addFiles, this, QOverload<const QStringList&>::of(&FileViewSvnPlugin::addFiles));
connect(svnCommitDialog, &SvnCommitDialog::commit, this, &FileViewSvnPlugin::commitFiles);
......@@ -409,6 +416,19 @@ void FileViewSvnPlugin::revertFiles()
i18nc("@info:status", "Reverted files from SVN repository."));
}
void FileViewSvnPlugin::logDialog()
{
SvnLogDialog *svnLogDialog = new SvnLogDialog(m_contextDir);
connect(svnLogDialog, &SvnLogDialog::errorMessage, this, &FileViewSvnPlugin::errorMessage);
connect(svnLogDialog, &SvnLogDialog::operationCompletedMessage, this, &FileViewSvnPlugin::operationCompletedMessage);
connect(svnLogDialog, &SvnLogDialog::diffAgainstWorkingCopy, this, &FileViewSvnPlugin::diffAgainstWorkingCopy);
connect(svnLogDialog, &SvnLogDialog::diffBetweenRevs, this, &FileViewSvnPlugin::diffBetweenRevs);
svnLogDialog->setAttribute(Qt::WA_DeleteOnClose);
svnLogDialog->show();
}
void FileViewSvnPlugin::slotOperationCompleted(int exitCode, QProcess::ExitStatus exitStatus)
{
m_pendingOperation = false;
......@@ -462,19 +482,23 @@ void FileViewSvnPlugin::diffFile(const QString& filePath)
// lines or set maximum number for this.
// With a maximum number (2147483647) 'svn diff' starts to work slowly.
diffAgainstWorkingCopy(filePath, SVNCommands::localRevision(filePath));
}
void FileViewSvnPlugin::diffAgainstWorkingCopy(const QString& localFilePath, ulong rev)
{
QTemporaryFile *file = new QTemporaryFile(this);
// TODO: Calling a blocking operation: with a slow connection this might take some time. Work
// should be done in a separate thread or process.
if (!SVNCommands::exportLocalFile(filePath, SVNCommands::localRevision(filePath), file)) {
if (!SVNCommands::exportFile(QUrl::fromLocalFile(localFilePath), rev, file)) {
emit errorMessage(i18nc("@info:status", "Could not show local SVN changes for a file: could not get file."));
file->deleteLater();
return;
}
const bool started = QProcess::startDetached(
QLatin1String("kompare"),
QStringList {
file->fileName(),
filePath
localFilePath
}
);
if (!started) {
......@@ -483,6 +507,36 @@ void FileViewSvnPlugin::diffFile(const QString& filePath)
}
}
void FileViewSvnPlugin::diffBetweenRevs(const QString& remoteFilePath, ulong rev1, ulong rev2)
{
QTemporaryFile *file1 = new QTemporaryFile(this);
QTemporaryFile *file2 = new QTemporaryFile(this);
if (!SVNCommands::exportFile(QUrl::fromLocalFile(remoteFilePath), rev1, file1)) {
emit errorMessage(i18nc("@info:status", "Could not show local SVN changes for a file: could not get file."));
file1->deleteLater();
return;
}
if (!SVNCommands::exportFile(QUrl::fromLocalFile(remoteFilePath), rev2, file2)) {
emit errorMessage(i18nc("@info:status", "Could not show local SVN changes for a file: could not get file."));
file1->deleteLater();
file2->deleteLater();
return;
}
const bool started = QProcess::startDetached(
QLatin1String("kompare"),
QStringList {
file2->fileName(),
file1->fileName()
}
);
if (!started) {
emit errorMessage(i18nc("@info:status", "Could not show local SVN changes: could not start kompare."));
file1->deleteLater();
file2->deleteLater();
}
}
void FileViewSvnPlugin::addFiles(const QStringList& filesPath)
{
for (const auto &i : qAsConst(filesPath)) {
......@@ -598,6 +652,7 @@ QList<QAction*> FileViewSvnPlugin::directoryActions(const KFileItem& directory)
actions.append(m_addAction);
actions.append(m_removeAction);
actions.append(m_revertAction);
actions.append(m_logAction);
return actions;
}
......
......@@ -61,6 +61,7 @@ private slots:
void addFiles();
void removeFiles();
void revertFiles();
void logDialog();
void slotOperationCompleted(int exitCode, QProcess::ExitStatus exitStatus);
void slotOperationError();
......@@ -69,6 +70,8 @@ private slots:
void revertFiles(const QStringList& filesPath);
void diffFile(const QString& filePath);
void diffAgainstWorkingCopy(const QString& localFilePath, ulong rev);
void diffBetweenRevs(const QString& remoteFilePath, ulong rev1, ulong rev2);
void addFiles(const QStringList& filesPath);
void commitFiles(const QStringList& context, const QString& msg);
......@@ -104,6 +107,7 @@ private:
QAction* m_removeAction;
QAction* m_revertAction;
QAction* m_showUpdatesAction;
QAction* m_logAction;
QString m_command;
QStringList m_arguments;
......
This diff is collapsed.
......@@ -22,6 +22,7 @@
#define SVNCOMMANDS_H
#include <QString>
#include <QDateTime>
#include <QtGlobal>
#include <Dolphin/KVersionControlPlugin>
......@@ -29,6 +30,29 @@
class QTemporaryFile;
class QFileDevice;
/**
* Path information for log entry.
*/
struct affectedPath {
QString action; ///< Action type: "D" for delete, "M" for modified, etc.
bool propMods; ///< Property changes by commit.
bool textMods; ///< File changes by commit.
QString kind; ///< Path type: "file", "dir", etc.
QString path; ///< Path itself.
};
/**
* A single log entry.
*/
struct logEntry {
ulong revision; ///< Revision number.
QString author; ///< Commit author.
QDateTime date; ///< Commit time and date.
QVector<affectedPath> affectedPaths; ///< Affected paths (files or dirs).
QString msg; ///< Commit message.
};
/**
* \brief SVN support functions.
*
......@@ -43,9 +67,21 @@ public:
* \return Local revision, 0 in case of error.
*
* \note This function uses only local SVN data without connection to a remote so it's fast.
* \sa remoteRevision()
*/
static ulong localRevision(const QString& filePath);
/**
* Returns file \p filePath remote revision. Remote revision means last known SVN repository file
* revision. This function uses only current SVN data and doesn't connect to a remote.
*
* \return Local revision, 0 in case of error.
*
* \note This function uses only local SVN data without connection to a remote so it's fast.
* \sa localRevision()
*/
static ulong remoteRevision(const QString& filePath);
/**
* For file \p filePath return its full remote repository URL path.
*
......@@ -56,26 +92,75 @@ public:
static QString remoteItemUrl(const QString& filePath);
/**
* Export remote URL \p remoteUrl at revision \p rev to a file \p file. File should already be
* opened or ready to be opened. Freeing resources is up to the caller.
* From a file \p filePath returns full remote repository URL in which this file located. For
* every file in the repository URL is the same, i.e. returns path used for initial 'svn co'.
*
* \return True if export success, false either.
* \return Remote path, empty QString in case of error.
*
* \note \p file should already be created with \p new.
* \note This function uses only local SVN data without connection to a remote so it's fast.
*/
static bool exportRemoteFile(const QString& remoteUrl, ulong rev, QFileDevice *file);
static bool exportRemoteFile(const QString& remoteUrl, ulong rev, QTemporaryFile *file);
static QString remoteRootUrl(const QString& filePath);
/**
* Export local file \p filePath at revision \p rev to a file \p file. File should already be
* opened or ready to be opened. Freeing resources is up to the caller.
* From a file \p filePath returns relative repository URL in which this file located. So,
* for example, a root repository file "file.txt" will have relative URL "^/file.txt".
*
* \return Relative repository URL, 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 remoteRelativeUrl(const QString& filePath);
/**
* Updates selected \p filePath to revision \p revision. \p filePath could be a sigle 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.
*/
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.
* 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.
*/
static bool revertLocalChanges(const QString& filePath);
/**
* Reverts selected \p filePath to revision \p revision. \p filePath could be a sigle 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);
/**
* 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
* already be opened or ready to be opened. Freeing resources is up to the caller.
*
* \return True if export success, false either.
*
* \note \p file should already be created with \p new.
*/
static bool exportLocalFile(const QString& filePath, ulong rev, QFileDevice *file);
static bool exportLocalFile(const QString& filePath, ulong rev, QTemporaryFile *file);
static bool exportFile(const QUrl& path, ulong rev, QFileDevice *file);
static bool exportFile(const QUrl& path, ulong rev, QTemporaryFile *file);
/**
* Get SVN log for a following \p filePath (could be a directory or separate file, relative or
* absolute paths accepted). Log starts from revision \p fromRevision and goes for \p maxEntries
* previous revisions. The default revision (0) means current revision.
*
* \return Full log, empty QVector in case of error.
*
* \note This function is really time consuming.
*/
static QSharedPointer< QVector<logEntry> > getLog(const QString& filePath, uint maxEntries = 255, ulong fromRevision = 0);
};
#endif // SVNCOMMANDS_H
......@@ -70,11 +70,16 @@ QStringList makeContext(const QStringList &list, const QHash<QString, KVersionCo
}
struct svnInfo_t {
QString filePath;
KVersionControlPlugin::ItemVersion fileVersion;
struct svnCommitEntryInfo_t {
svnCommitEntryInfo_t() :
localPath(QString()),
fileVersion( KVersionControlPlugin::NormalVersion )
{}
QString localPath; ///< Affected local path.
KVersionControlPlugin::ItemVersion fileVersion; ///< File status in terms of KVersionControlPlugin
};
Q_DECLARE_METATYPE(svnInfo_t);
Q_DECLARE_METATYPE(svnCommitEntryInfo_t);
enum columns_t {
columnPath,
......@@ -126,21 +131,21 @@ SvnCommitDialog::SvnCommitDialog(const QHash<QString, KVersionControlPlugin::Ite
m_actRevertFile = new QAction(i18nc("@item:inmenu", "Revert"), this);
m_actRevertFile->setIcon(QIcon::fromTheme("document-revert"));
connect(m_actRevertFile, &QAction::triggered, [this] () {
const QString filePath = m_actRevertFile->data().value<svnInfo_t>().filePath;
const QString filePath = m_actRevertFile->data().value<svnCommitEntryInfo_t>().localPath;
emit revertFiles(QStringList() << filePath);
} );
m_actDiffFile = new QAction(i18nc("@item:inmenu", "Show changes"), this);
m_actDiffFile->setIcon(QIcon::fromTheme("view-split-left-right"));
connect(m_actDiffFile, &QAction::triggered, [this] () {
const QString filePath = m_actDiffFile->data().value<svnInfo_t>().filePath;
const QString filePath = m_actDiffFile->data().value<svnCommitEntryInfo_t>().localPath;
emit diffFile(filePath);
} );
m_actAddFile = new QAction(i18nc("@item:inmenu", "Add file"), this);
m_actAddFile->setIcon(QIcon::fromTheme("list-add"));
connect(m_actAddFile, &QAction::triggered, [this] () {
const QString filePath = m_actAddFile->data().value<svnInfo_t>().filePath;
const QString filePath = m_actAddFile->data().value<svnCommitEntryInfo_t>().localPath;
emit addFiles(QStringList() << filePath);
} );
......@@ -208,7 +213,9 @@ void SvnCommitDialog::refreshChangesList()
m_changes->setItem(row, columnStatus, status);
row++;
svnInfo_t info { it.key(), it.value() };
svnCommitEntryInfo_t info;
info.localPath = it.key();
info.fileVersion = it.value();
path->setData(Qt::UserRole, QVariant::fromValue(info));
status->setData(Qt::UserRole, QVariant::fromValue(info));
......@@ -272,7 +279,7 @@ void SvnCommitDialog::contextMenu(const QPoint& pos)
m_actDiffFile->setEnabled(false);
m_actAddFile->setEnabled(false);
const svnInfo_t info = data.value<svnInfo_t>();
const svnCommitEntryInfo_t info = data.value<svnCommitEntryInfo_t>();
switch(info.fileVersion) {
case KVersionControlPlugin::UnversionedVersion:
m_actAddFile->setEnabled(true);
......
This diff is collapsed.
/***************************************************************************
* 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 SVNLOGDIALOG_H
#define SVNLOGDIALOG_H
#include <QWidget>
#include <QVector>
#include "svncommands.h"
#include "ui_svnlogdialog.h"
class SvnLogDialog : public QWidget {
Q_OBJECT
public:
SvnLogDialog(const QString& contextDir, QWidget *parent = nullptr);
virtual ~SvnLogDialog() override;
public slots:
void setCurrentRevision(ulong revision);
void refreshLog();
void on_tLog_currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn);
signals:
void errorMessage(const QString& msg);
void operationCompletedMessage(const QString& msg);
void diffAgainstWorkingCopy(const QString& localFilePath, ulong rev);
void diffBetweenRevs(const QString& remoteFilePath, ulong rev1, ulong rev2);
private slots:
void showContextMenuLog(const QPoint &pos);
void showContextMenuChangesList(const QPoint &pos);
void updateRepoToRevision();
void revertRepoToRevision();
void revertFileToRevision();
private:
Ui::SvnLogDialog m_ui;
QSharedPointer< QVector<logEntry> > m_log;
const QString m_contextDir;
uint m_logLength;
QAction *m_updateToRev;
QAction *m_revertToRev;
QAction *m_diffFilePrev;
QAction *m_diffFileCurrent;
QAction *m_fileRevertToRev;
};
#endif // SVNLOGDIALOG_H
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SvnLogDialog</class>
<widget class="QWidget" name="SvnLogDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>726</width>
<height>654</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>SVN Log</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="5,1,1,1,1,0">
<item>
<widget class="QTableWidget" name="tLog">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
<property name="rowCount">
<number>1</number>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderStretchLastSection">
<bool>false</bool>
</attribute>
<row/>
<column>
<property name="text">
<string>Revision</string>
</property>
</column>
<column>
<property name="text">
<string>Author</string>
</property>
</column>
<column>
<property name="text">
<string>Date</string>
</property>
</column>
<column>
<property name="text">
<string>Message</string>
</property>
</column>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="teMessage"/>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="lPaths">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="pbNext100">
<property name="text">
<string>Next 100</string>
</property>
<property name="icon">
<iconset theme="go-down"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pbRefresh">
<property name="text">
<string>Refresh</string>
</property>
<property name="icon">
<iconset theme="view-refresh"/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>448</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pbOk">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>OK</string>
</property>
<property name="icon">
<iconset theme="dialog-ok"/>
</property>
</widget>
</item>
</layout>
</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