Improved Subversion test plugin to allow committing, updating, diffing, adding and removing of files. As soon as the test plugin gets moved to kdesdk, the code should be improved to use the libsvn interface.

svn path=/trunk/KDE/kdebase/apps/; revision=1002839
This commit is contained in:
Peter Penz 2009-07-27 05:31:48 +00:00
parent 47d5003283
commit 66ad27aba1
9 changed files with 313 additions and 107 deletions

View file

@ -33,7 +33,9 @@
DolphinFileItemDelegate::DolphinFileItemDelegate(QObject* parent) : DolphinFileItemDelegate::DolphinFileItemDelegate(QObject* parent) :
KFileItemDelegate(parent), KFileItemDelegate(parent),
m_hasMinimizedNameColumn(false) m_hasMinimizedNameColumn(false),
m_cachedSize(),
m_cachedEmblems()
{ {
} }
@ -65,12 +67,10 @@ void DolphinFileItemDelegate::paint(QPainter* painter,
const QVariant data = dolphinModel->data(revisionIndex, Qt::DecorationRole); const QVariant data = dolphinModel->data(revisionIndex, Qt::DecorationRole);
const RevisionControlPlugin::RevisionState state = static_cast<RevisionControlPlugin::RevisionState>(data.toInt()); const RevisionControlPlugin::RevisionState state = static_cast<RevisionControlPlugin::RevisionState>(data.toInt());
if (state != RevisionControlPlugin::LocalRevision) { if (state != RevisionControlPlugin::UnversionedRevision) {
// TODO: extend KFileItemDelegate to be able to get the icon boundaries const QRect rect = iconRect(option, index);
const QRect iconRect(option.rect.x(), option.rect.y(), const QPixmap emblem = emblemForState(state, rect.size());
KIconLoader::SizeSmall, KIconLoader::SizeSmall); painter->drawPixmap(rect.x(), rect.y() + rect.height() - emblem.height(), emblem);
const QPixmap emblem = emblemForState(state, iconRect.size());
painter->drawPixmap(iconRect.x(), iconRect.y(), emblem);
} }
} }
} }
@ -105,23 +105,42 @@ void DolphinFileItemDelegate::adjustOptionWidth(QStyleOptionViewItemV4& option,
} }
} }
QPixmap DolphinFileItemDelegate::emblemForState(RevisionControlPlugin::RevisionState state, const QSize& size) QPixmap DolphinFileItemDelegate::emblemForState(RevisionControlPlugin::RevisionState state, const QSize& size) const
{ {
// TODO #1: all icons that are use here will be replaced by revision control emblems provided by the // TODO: all icons that are use here will be replaced by revision control emblems provided by the
// Oxygen team before KDE 4.4 // Oxygen team before KDE 4.4
// TODO #2: cache the icons Q_ASSERT(state <= RevisionControlPlugin::ConflictingRevision);
switch (state) { if ((m_cachedSize != size) || !m_cachedEmblems[state].isNull()) {
case RevisionControlPlugin::LatestRevision: m_cachedSize = size;
return KIcon("dialog-ok-apply").pixmap(size);
case RevisionControlPlugin::ConflictingRevision: const int iconHeight = size.height();
return KIcon("application-exit").pixmap(size); int emblemHeight = KIconLoader::SizeSmall;
case RevisionControlPlugin::UpdateRequiredRevision: if (iconHeight >= KIconLoader::SizeEnormous) {
return KIcon("rating").pixmap(size); emblemHeight = KIconLoader::SizeMedium;
case RevisionControlPlugin::EditingRevision: } else if (iconHeight >= KIconLoader::SizeLarge) {
return KIcon("emblem-important").pixmap(size); emblemHeight = KIconLoader::SizeSmallMedium;
default: } else if (iconHeight >= KIconLoader::SizeMedium) {
break; emblemHeight = KIconLoader::SizeSmall;
} else {
// TODO: it depends on the final icons whether a smaller size works
emblemHeight = KIconLoader::SizeSmall /* / 2 */;
}
const QSize emblemSize(emblemHeight, emblemHeight);
for (int i = 0; i <= RevisionControlPlugin::ConflictingRevision; ++i) {
QString iconName;
switch (state) {
case RevisionControlPlugin::NormalRevision: iconName = "dialog-ok-apply"; break;
case RevisionControlPlugin::UpdateRequiredRevision: iconName = "rating"; break;
case RevisionControlPlugin::LocallyModifiedRevision: iconName = "emblem-important"; break;
case RevisionControlPlugin::AddedRevision: iconName = "list-add"; break;
case RevisionControlPlugin::ConflictingRevision: iconName = "application-exit"; break;
default: Q_ASSERT(false); break;
}
m_cachedEmblems[i] = KIcon(iconName).pixmap(emblemSize);
}
} }
return QPixmap(); return m_cachedEmblems[state];
} }

View file

@ -66,10 +66,12 @@ private:
const DolphinModel* dolphinModel, const DolphinModel* dolphinModel,
const QModelIndex& index); const QModelIndex& index);
static QPixmap emblemForState(RevisionControlPlugin::RevisionState state, const QSize& size); QPixmap emblemForState(RevisionControlPlugin::RevisionState state, const QSize& size) const;
private: private:
bool m_hasMinimizedNameColumn; bool m_hasMinimizedNameColumn;
mutable QSize m_cachedSize;
mutable QPixmap m_cachedEmblems[RevisionControlPlugin::ConflictingRevision + 1];
}; };
inline void DolphinFileItemDelegate::setMinimizedNameColumn(bool minimized) inline void DolphinFileItemDelegate::setMinimizedNameColumn(bool minimized)

View file

@ -69,7 +69,7 @@ bool DolphinModel::setData(const QModelIndex& index, const QVariant& value, int
const QPersistentModelIndex key = index; const QPersistentModelIndex key = index;
const RevisionControlPlugin::RevisionState state = static_cast<RevisionControlPlugin::RevisionState>(value.toInt()); const RevisionControlPlugin::RevisionState state = static_cast<RevisionControlPlugin::RevisionState>(value.toInt());
if (m_revisionHash.value(key, RevisionControlPlugin::LocalRevision) != state) { if (m_revisionHash.value(key, RevisionControlPlugin::UnversionedRevision) != state) {
if (!m_hasRevisionData) { if (!m_hasRevisionData) {
connect(this, SIGNAL(rowsRemoved (const QModelIndex&, int, int)), connect(this, SIGNAL(rowsRemoved (const QModelIndex&, int, int)),
this, SLOT(slotRowsRemoved(const QModelIndex&, int, int))); this, SLOT(slotRowsRemoved(const QModelIndex&, int, int)));
@ -96,22 +96,22 @@ QVariant DolphinModel::data(const QModelIndex& index, int role) const
case Qt::DecorationRole: case Qt::DecorationRole:
if (index.column() == DolphinModel::Revision) { if (index.column() == DolphinModel::Revision) {
return m_revisionHash.value(index, RevisionControlPlugin::LocalRevision); return m_revisionHash.value(index, RevisionControlPlugin::UnversionedRevision);
} }
break; break;
case Qt::DisplayRole: case Qt::DisplayRole:
if (index.column() == DolphinModel::Revision) { if (index.column() == DolphinModel::Revision) {
switch (m_revisionHash.value(index, RevisionControlPlugin::LocalRevision)) { switch (m_revisionHash.value(index, RevisionControlPlugin::UnversionedRevision)) {
case RevisionControlPlugin::LatestRevision: case RevisionControlPlugin::NormalRevision:
return i18nc("@item::intable", "Latest"); return i18nc("@item::intable", "Normal");
case RevisionControlPlugin::EditingRevision: case RevisionControlPlugin::LocallyModifiedRevision:
return i18nc("@item::intable", "Editing"); return i18nc("@item::intable", "Locally modified");
case RevisionControlPlugin::UpdateRequiredRevision: case RevisionControlPlugin::UpdateRequiredRevision:
return i18nc("@item::intable", "Update required"); return i18nc("@item::intable", "Update required");
case RevisionControlPlugin::LocalRevision: case RevisionControlPlugin::UnversionedRevision:
default: default:
return i18nc("@item::intable", "Local"); return i18nc("@item::intable", "Unversioned");
} }
} }
break; break;
@ -141,6 +141,12 @@ int DolphinModel::columnCount(const QModelIndex& parent) const
return KDirModel::columnCount(parent) + (ExtraColumnCount - ColumnCount); return KDirModel::columnCount(parent) + (ExtraColumnCount - ColumnCount);
} }
void DolphinModel::clearRevisionData()
{
m_revisionHash.clear();
m_hasRevisionData = false;
}
bool DolphinModel::hasRevisionData() const bool DolphinModel::hasRevisionData() const
{ {
return m_hasRevisionData; return m_hasRevisionData;
@ -148,11 +154,11 @@ bool DolphinModel::hasRevisionData() const
void DolphinModel::slotRowsRemoved(const QModelIndex& parent, int start, int end) void DolphinModel::slotRowsRemoved(const QModelIndex& parent, int start, int end)
{ {
Q_ASSERT(hasRevisionData()); if (m_hasRevisionData) {
const int column = parent.column();
const int column = parent.column(); for (int row = start; row <= end; ++row) {
for (int row = start; row <= end; ++row) { m_revisionHash.remove(parent.child(row, column));
m_revisionHash.remove(parent.child(row, column)); }
} }
} }

View file

@ -46,6 +46,7 @@ public:
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const; virtual int columnCount(const QModelIndex& parent = QModelIndex()) const;
void clearRevisionData();
bool hasRevisionData() const; bool hasRevisionData() const;
private slots: private slots:

View file

@ -522,6 +522,10 @@ void DolphinView::updateView(const KUrl& url, const KUrl& rootUrl)
loadDirectory(url); loadDirectory(url);
} }
// When changing the URL there is no need to keep the revision
// data of the previous URL.
m_dolphinModel->clearRevisionData();
emit startedPathLoading(url); emit startedPathLoading(url);
} }

View file

@ -26,6 +26,7 @@
#include <QAbstractProxyModel> #include <QAbstractProxyModel>
#include <QAbstractItemView> #include <QAbstractItemView>
#include <QMutexLocker>
#include <QTimer> #include <QTimer>
/** /**
@ -36,7 +37,7 @@
class UpdateItemStatesThread : public QThread class UpdateItemStatesThread : public QThread
{ {
public: public:
UpdateItemStatesThread(QObject* parent); UpdateItemStatesThread(QObject* parent, QMutex* pluginMutex);
void setData(RevisionControlPlugin* plugin, void setData(RevisionControlPlugin* plugin,
const QList<RevisionControlObserver::ItemState>& itemStates); const QList<RevisionControlObserver::ItemState>& itemStates);
QList<RevisionControlObserver::ItemState> itemStates() const; QList<RevisionControlObserver::ItemState> itemStates() const;
@ -46,11 +47,13 @@ protected:
private: private:
RevisionControlPlugin* m_plugin; RevisionControlPlugin* m_plugin;
QMutex* m_pluginMutex;
QList<RevisionControlObserver::ItemState> m_itemStates; QList<RevisionControlObserver::ItemState> m_itemStates;
}; };
UpdateItemStatesThread::UpdateItemStatesThread(QObject* parent) : UpdateItemStatesThread::UpdateItemStatesThread(QObject* parent, QMutex* pluginMutex) :
QThread(parent) QThread(parent),
m_pluginMutex(pluginMutex)
{ {
} }
@ -68,7 +71,8 @@ void UpdateItemStatesThread::run()
// it is assumed that all items have the same parent directory // it is assumed that all items have the same parent directory
const QString directory = m_itemStates.first().item.url().directory(KUrl::AppendTrailingSlash); const QString directory = m_itemStates.first().item.url().directory(KUrl::AppendTrailingSlash);
QMutexLocker locker(m_pluginMutex);
if (m_plugin->beginRetrieval(directory)) { if (m_plugin->beginRetrieval(directory)) {
const int count = m_itemStates.count(); const int count = m_itemStates.count();
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
@ -83,7 +87,7 @@ QList<RevisionControlObserver::ItemState> UpdateItemStatesThread::itemStates() c
return m_itemStates; return m_itemStates;
} }
// --- // ------------------------------------------------------------------------------------------------
RevisionControlObserver::RevisionControlObserver(QAbstractItemView* view) : RevisionControlObserver::RevisionControlObserver(QAbstractItemView* view) :
QObject(view), QObject(view),
@ -93,6 +97,7 @@ RevisionControlObserver::RevisionControlObserver(QAbstractItemView* view) :
m_dirLister(0), m_dirLister(0),
m_dolphinModel(0), m_dolphinModel(0),
m_dirVerificationTimer(0), m_dirVerificationTimer(0),
m_pluginMutex(QMutex::Recursive),
m_plugin(0), m_plugin(0),
m_updateItemStatesThread(0) m_updateItemStatesThread(0)
{ {
@ -100,8 +105,8 @@ RevisionControlObserver::RevisionControlObserver(QAbstractItemView* view) :
QAbstractProxyModel* proxyModel = qobject_cast<QAbstractProxyModel*>(view->model()); QAbstractProxyModel* proxyModel = qobject_cast<QAbstractProxyModel*>(view->model());
m_dolphinModel = (proxyModel == 0) ? m_dolphinModel = (proxyModel == 0) ?
qobject_cast<DolphinModel*>(view->model()) : qobject_cast<DolphinModel*>(view->model()) :
qobject_cast<DolphinModel*>(proxyModel->sourceModel()); qobject_cast<DolphinModel*>(proxyModel->sourceModel());
if (m_dolphinModel != 0) { if (m_dolphinModel != 0) {
m_dirLister = m_dolphinModel->dirLister(); m_dirLister = m_dolphinModel->dirLister();
connect(m_dirLister, SIGNAL(completed()), connect(m_dirLister, SIGNAL(completed()),
@ -129,6 +134,7 @@ RevisionControlObserver::~RevisionControlObserver()
QList<QAction*> RevisionControlObserver::contextMenuActions(const KFileItemList& items) const QList<QAction*> RevisionControlObserver::contextMenuActions(const KFileItemList& items) const
{ {
if (m_dolphinModel->hasRevisionData() && (m_plugin != 0)) { if (m_dolphinModel->hasRevisionData() && (m_plugin != 0)) {
QMutexLocker locker(&m_pluginMutex);
return m_plugin->contextMenuActions(items); return m_plugin->contextMenuActions(items);
} }
return QList<QAction*>(); return QList<QAction*>();
@ -137,6 +143,7 @@ QList<QAction*> RevisionControlObserver::contextMenuActions(const KFileItemList&
QList<QAction*> RevisionControlObserver::contextMenuActions(const QString& directory) const QList<QAction*> RevisionControlObserver::contextMenuActions(const QString& directory) const
{ {
if (m_dolphinModel->hasRevisionData() && (m_plugin != 0)) { if (m_dolphinModel->hasRevisionData() && (m_plugin != 0)) {
QMutexLocker locker(&m_pluginMutex);
return m_plugin->contextMenuActions(directory); return m_plugin->contextMenuActions(directory);
} }
@ -162,28 +169,44 @@ void RevisionControlObserver::verifyDirectory()
revisionControlUrl.addPath(m_plugin->fileName()); revisionControlUrl.addPath(m_plugin->fileName());
const KFileItem item = m_dirLister->findByUrl(revisionControlUrl); const KFileItem item = m_dirLister->findByUrl(revisionControlUrl);
if (item.isNull() && m_revisionedDirectory) {
// The directory is not versioned. Reset the verification timer to a higher bool foundRevisionInfo = !item.isNull();
// value, so that browsing through non-versioned directories is not slown down if (!foundRevisionInfo && m_revisionedDirectory) {
// by an immediate verification. // Revision control systems like Git provide the revision information
m_dirVerificationTimer->setInterval(500); // file only in the root directory. Check whether the revision information file can
m_revisionedDirectory = false; // be found in one of the parent directories.
disconnect(m_dirLister, SIGNAL(refreshItems(const QList<QPair<KFileItem,KFileItem>>&)),
this, SLOT(delayedDirectoryVerification())); // TODO...
disconnect(m_dirLister, SIGNAL(newItems(const KFileItemList&)), }
this, SLOT(delayedDirectoryVerification()));
} else if (!item.isNull()) { if (foundRevisionInfo) {
if (!m_revisionedDirectory) { if (!m_revisionedDirectory) {
m_revisionedDirectory = true;
// The directory is versioned. Assume that the user will further browse through // The directory is versioned. Assume that the user will further browse through
// versioned directories and decrease the verification timer. // versioned directories and decrease the verification timer.
m_dirVerificationTimer->setInterval(100); m_dirVerificationTimer->setInterval(100);
m_revisionedDirectory = true;
connect(m_dirLister, SIGNAL(refreshItems(const QList<QPair<KFileItem,KFileItem>>&)), connect(m_dirLister, SIGNAL(refreshItems(const QList<QPair<KFileItem,KFileItem>>&)),
this, SLOT(delayedDirectoryVerification())); this, SLOT(delayedDirectoryVerification()));
connect(m_dirLister, SIGNAL(newItems(const KFileItemList&)), connect(m_dirLister, SIGNAL(newItems(const KFileItemList&)),
this, SLOT(delayedDirectoryVerification())); this, SLOT(delayedDirectoryVerification()));
connect(m_plugin, SIGNAL(revisionStatesChanged(const QString&)),
this, SLOT(delayedDirectoryVerification()));
} }
updateItemStates(); updateItemStates();
} else if (m_revisionedDirectory) {
m_revisionedDirectory = false;
// The directory is not versioned. Reset the verification timer to a higher
// value, so that browsing through non-versioned directories is not slown down
// by an immediate verification.
m_dirVerificationTimer->setInterval(500);
disconnect(m_dirLister, SIGNAL(refreshItems(const QList<QPair<KFileItem,KFileItem>>&)),
this, SLOT(delayedDirectoryVerification()));
disconnect(m_dirLister, SIGNAL(newItems(const KFileItemList&)),
this, SLOT(delayedDirectoryVerification()));
disconnect(m_plugin, SIGNAL(revisionStatesChanged(const QString&)),
this, SLOT(delayedDirectoryVerification()));
} }
} }
@ -216,7 +239,7 @@ void RevisionControlObserver::updateItemStates()
{ {
Q_ASSERT(m_plugin != 0); Q_ASSERT(m_plugin != 0);
if (m_updateItemStatesThread == 0) { if (m_updateItemStatesThread == 0) {
m_updateItemStatesThread = new UpdateItemStatesThread(this); m_updateItemStatesThread = new UpdateItemStatesThread(this, &m_pluginMutex);
connect(m_updateItemStatesThread, SIGNAL(finished()), connect(m_updateItemStatesThread, SIGNAL(finished()),
this, SLOT(applyUpdatedItemStates())); this, SLOT(applyUpdatedItemStates()));
} }
@ -238,8 +261,8 @@ void RevisionControlObserver::updateItemStates()
ItemState itemState; ItemState itemState;
itemState.index = index; itemState.index = index;
itemState.item = m_dolphinModel->itemForIndex(index); itemState.item = m_dolphinModel->itemForIndex(index);
itemState.revision = RevisionControlPlugin::LocalRevision; itemState.revision = RevisionControlPlugin::UnversionedRevision;
itemStates.append(itemState); itemStates.append(itemState);
} }

View file

@ -25,6 +25,7 @@
#include <kfileitem.h> #include <kfileitem.h>
#include <revisioncontrolplugin.h> #include <revisioncontrolplugin.h>
#include <QList> #include <QList>
#include <QMutex>
#include <QObject> #include <QObject>
#include <QPersistentModelIndex> #include <QPersistentModelIndex>
#include <QString> #include <QString>
@ -82,6 +83,7 @@ private:
QTimer* m_dirVerificationTimer; QTimer* m_dirVerificationTimer;
mutable QMutex m_pluginMutex;
RevisionControlPlugin* m_plugin; RevisionControlPlugin* m_plugin;
UpdateItemStatesThread* m_updateItemStatesThread; UpdateItemStatesThread* m_updateItemStatesThread;

View file

@ -19,14 +19,6 @@
#include "revisioncontrolplugin.h" #include "revisioncontrolplugin.h"
#include <kaction.h>
#include <kicon.h>
#include <klocale.h>
#include <kfileitem.h>
#include <QDir>
#include <QString>
#include <QTextStream>
RevisionControlPlugin::RevisionControlPlugin() RevisionControlPlugin::RevisionControlPlugin()
{ {
} }
@ -39,28 +31,59 @@ RevisionControlPlugin::~RevisionControlPlugin()
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#include <kaction.h>
#include <kdialog.h>
#include <kicon.h>
#include <klocale.h>
#include <krun.h>
#include <kshell.h>
#include <kfileitem.h>
#include <kvbox.h>
#include <QDir>
#include <QLabel>
#include <QString>
#include <QTextEdit>
#include <QTextStream>
SubversionPlugin::SubversionPlugin() : SubversionPlugin::SubversionPlugin() :
m_directory(), m_retrievalDir(),
m_revisionInfoHash(), m_revisionInfoHash(),
m_updateAction(0), m_updateAction(0),
m_showLocalChangesAction(0),
m_commitAction(0), m_commitAction(0),
m_addAction(0), m_addAction(0),
m_removeAction(0) m_removeAction(0),
m_contextDir(),
m_contextItems()
{ {
m_updateAction = new KAction(this); m_updateAction = new KAction(this);
m_updateAction->setIcon(KIcon("view-refresh")); m_updateAction->setIcon(KIcon("view-refresh"));
m_updateAction->setText(i18nc("@item:inmenu", "SVN Update")); m_updateAction->setText(i18nc("@item:inmenu", "SVN Update"));
connect(m_updateAction, SIGNAL(triggered()),
this, SLOT(updateFiles()));
m_showLocalChangesAction = new KAction(this);
m_showLocalChangesAction->setIcon(KIcon("view-split-left-right"));
m_showLocalChangesAction->setText(i18nc("@item:inmenu", "Show Local SVN Changes"));
connect(m_showLocalChangesAction, SIGNAL(triggered()),
this, SLOT(showLocalChanges()));
m_commitAction = new KAction(this); m_commitAction = new KAction(this);
m_commitAction->setText(i18nc("@item:inmenu", "SVN Commit...")); m_commitAction->setText(i18nc("@item:inmenu", "SVN Commit..."));
connect(m_commitAction, SIGNAL(triggered()),
this, SLOT(commitFiles()));
m_addAction = new KAction(this); m_addAction = new KAction(this);
m_addAction->setIcon(KIcon("list-add")); m_addAction->setIcon(KIcon("list-add"));
m_addAction->setText(i18nc("@item:inmenu", "SVN Add")); m_addAction->setText(i18nc("@item:inmenu", "SVN Add"));
connect(m_addAction, SIGNAL(triggered()),
this, SLOT(addFiles()));
m_removeAction = new KAction(this); m_removeAction = new KAction(this);
m_removeAction->setIcon(KIcon("list-remove")); m_removeAction->setIcon(KIcon("list-remove"));
m_removeAction->setText(i18nc("@item:inmenu", "SVN Delete")); m_removeAction->setText(i18nc("@item:inmenu", "SVN Delete"));
connect(m_removeAction, SIGNAL(triggered()),
this, SLOT(removeFiles()));
} }
SubversionPlugin::~SubversionPlugin() SubversionPlugin::~SubversionPlugin()
@ -75,7 +98,7 @@ QString SubversionPlugin::fileName() const
bool SubversionPlugin::beginRetrieval(const QString& directory) bool SubversionPlugin::beginRetrieval(const QString& directory)
{ {
Q_ASSERT(directory.endsWith('/')); Q_ASSERT(directory.endsWith('/'));
m_directory = directory; m_retrievalDir = directory;
const QString path = directory + ".svn/text-base/"; const QString path = directory + ".svn/text-base/";
QDir dir(path); QDir dir(path);
@ -83,15 +106,16 @@ bool SubversionPlugin::beginRetrieval(const QString& directory)
const int size = fileInfoList.size(); const int size = fileInfoList.size();
QString fileName; QString fileName;
for (int i = 0; i < size; ++i) { for (int i = 0; i < size; ++i) {
fileName = fileInfoList.at(i).fileName(); const QFileInfo fileInfo = fileInfoList.at(i);
fileName = fileInfo.fileName();
// Remove the ".svn-base" postfix to be able to compare the filenames // Remove the ".svn-base" postfix to be able to compare the filenames
// in a fast way in SubversionPlugin::revisionState(). // in a fast way in SubversionPlugin::revisionState().
fileName.chop(sizeof(".svn-base") / sizeof(char) - 1); fileName.chop(sizeof(".svn-base") / sizeof(char) - 1);
if (!fileName.isEmpty()) { if (!fileName.isEmpty()) {
RevisionInfo info; RevisionInfo info;
info.size = fileInfoList.at(i).size(); info.size = fileInfo.size();
info.timeStamp = fileInfoList.at(i).lastModified(); info.timeStamp = fileInfo.lastModified();
m_revisionInfoHash.insert(fileName, info); m_revisionInfoHash.insert(directory + fileName, info);
} }
} }
return size > 0; return size > 0;
@ -103,37 +127,63 @@ void SubversionPlugin::endRetrieval()
RevisionControlPlugin::RevisionState SubversionPlugin::revisionState(const KFileItem& item) RevisionControlPlugin::RevisionState SubversionPlugin::revisionState(const KFileItem& item)
{ {
const QString name = item.name(); const QString itemUrl = item.localPath();
if (item.isDir()) { if (item.isDir()) {
QFile file(m_directory + name + "/.svn"); QFile file(itemUrl + "/.svn");
if (file.open(QIODevice::ReadOnly)) { if (file.open(QIODevice::ReadOnly)) {
file.close(); file.close();
// TODO... return RevisionControlPlugin::NormalRevision;
return RevisionControlPlugin::LatestRevision;
} }
} else if (m_revisionInfoHash.contains(name)) { } else if (m_revisionInfoHash.contains(itemUrl)) {
const RevisionInfo info = m_revisionInfoHash.value(item.name()); const RevisionInfo info = m_revisionInfoHash.value(itemUrl);
const QDateTime localTimeStamp = item.time(KFileItem::ModificationTime).dateTime(); const QDateTime localTimeStamp = item.time(KFileItem::ModificationTime).dateTime();
const QDateTime versionedTimeStamp = info.timeStamp; const QDateTime versionedTimeStamp = info.timeStamp;
if (localTimeStamp > versionedTimeStamp) { if (localTimeStamp > versionedTimeStamp) {
if ((info.size != item.size()) || !equalRevisionContent(item.name())) { if ((info.size != item.size()) || !equalRevisionContent(item.name())) {
return RevisionControlPlugin::EditingRevision; return RevisionControlPlugin::LocallyModifiedRevision;
} }
} else if (localTimeStamp < versionedTimeStamp) { } else if (localTimeStamp < versionedTimeStamp) {
if ((info.size != item.size()) || !equalRevisionContent(item.name())) { if ((info.size != item.size()) || !equalRevisionContent(item.name())) {
return RevisionControlPlugin::UpdateRequiredRevision; return RevisionControlPlugin::UpdateRequiredRevision;
} }
} }
return RevisionControlPlugin::LatestRevision; return RevisionControlPlugin::NormalRevision;
} }
return RevisionControlPlugin::LocalRevision; return RevisionControlPlugin::UnversionedRevision;
} }
QList<QAction*> SubversionPlugin::contextMenuActions(const KFileItemList& items) const QList<QAction*> SubversionPlugin::contextMenuActions(const KFileItemList& items)
{ {
Q_UNUSED(items); Q_ASSERT(!items.isEmpty());
m_contextItems = items;
m_contextDir.clear();
// iterate all items and check the revision state to know which
// actions can be enabled
const int itemsCount = items.count();
int revisionedCount = 0;
int editingCount = 0;
foreach (const KFileItem& item, items) {
const RevisionState state = revisionState(item);
if (state != UnversionedRevision) {
++revisionedCount;
}
switch (state) {
case LocallyModifiedRevision:
case ConflictingRevision:
++editingCount;
break;
default:
break;
}
}
m_commitAction->setEnabled(editingCount > 0);
m_addAction->setEnabled(revisionedCount == 0);
m_removeAction->setEnabled(revisionedCount == itemsCount);
QList<QAction*> actions; QList<QAction*> actions;
actions.append(m_updateAction); actions.append(m_updateAction);
@ -143,24 +193,90 @@ QList<QAction*> SubversionPlugin::contextMenuActions(const KFileItemList& items)
return actions; return actions;
} }
QList<QAction*> SubversionPlugin::contextMenuActions(const QString& directory) const QList<QAction*> SubversionPlugin::contextMenuActions(const QString& directory)
{ {
Q_UNUSED(directory); m_contextDir = directory;
m_contextItems.clear();
QList<QAction*> actions; QList<QAction*> actions;
actions.append(m_updateAction); actions.append(m_updateAction);
actions.append(m_showLocalChangesAction);
actions.append(m_commitAction); actions.append(m_commitAction);
return actions; return actions;
} }
void SubversionPlugin::updateFiles()
{
execSvnCommand("update");
}
void SubversionPlugin::showLocalChanges()
{
Q_ASSERT(!m_contextDir.isEmpty());
Q_ASSERT(m_contextItems.isEmpty());
const QString command = "mkfifo /tmp/fifo; svn diff " +
KShell::quoteArg(m_contextDir) +
" > /tmp/fifo & kompare /tmp/fifo; rm /tmp/fifo";
KRun::runCommand(command, 0);
}
void SubversionPlugin::commitFiles()
{
KDialog dialog(0, Qt::Dialog);
KVBox* box = new KVBox(&dialog);
new QLabel(i18nc("@label", "Description:"), box);
QTextEdit* editor = new QTextEdit(box);
dialog.setMainWidget(box);
dialog.setCaption(i18nc("@title:window", "SVN Commit"));
dialog.setButtons(KDialog::Ok | KDialog::Cancel);
dialog.setDefaultButton(KDialog::Ok);
dialog.setButtonText(KDialog::Ok, i18nc("@action:button", "Commit"));
KConfigGroup dialogConfig(KSharedConfig::openConfig("dolphinrc"),
"SvnCommitDialog");
dialog.restoreDialogSize(dialogConfig);
if (dialog.exec() == QDialog::Accepted) {
const QString description = editor->toPlainText();
execSvnCommand("commit -m " + KShell::quoteArg(description));
}
dialog.saveDialogSize(dialogConfig, KConfigBase::Persistent);
}
void SubversionPlugin::addFiles()
{
execSvnCommand("add");
}
void SubversionPlugin::removeFiles()
{
execSvnCommand("remove");
}
void SubversionPlugin::execSvnCommand(const QString& svnCommand)
{
const QString command = "svn " + svnCommand + ' ';
if (!m_contextDir.isEmpty()) {
KRun::runCommand(command + KShell::quoteArg(m_contextDir), 0);
} else {
foreach (const KFileItem& item, m_contextItems) {
KRun::runCommand(command + KShell::quoteArg(item.localPath()), 0);
}
}
}
bool SubversionPlugin::equalRevisionContent(const QString& name) const bool SubversionPlugin::equalRevisionContent(const QString& name) const
{ {
QFile localFile(m_directory + '/' + name); QFile localFile(m_retrievalDir + '/' + name);
if (!localFile.open(QIODevice::ReadOnly | QIODevice::Text)) { if (!localFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false; return false;
} }
QFile revisionedFile(m_directory + "/.svn/text-base/" + name + ".svn-base"); QFile revisionedFile(m_retrievalDir + "/.svn/text-base/" + name + ".svn-base");
if (!revisionedFile.open(QIODevice::ReadOnly | QIODevice::Text)) { if (!revisionedFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false; return false;
} }
@ -175,4 +291,3 @@ bool SubversionPlugin::equalRevisionContent(const QString& name) const
return localText.atEnd() && revisionedText.atEnd(); return localText.atEnd() && revisionedText.atEnd();
} }

View file

@ -34,13 +34,7 @@ class QAction;
* @brief Base class for revision control plugins. * @brief Base class for revision control plugins.
* *
* Enables the file manager to show the revision state * Enables the file manager to show the revision state
* of a revisioned file. The methods * of a revisioned file.
* RevisionControlPlugin::beginRetrieval(),
* RevisionControlPlugin::endRetrieval() and
* RevisionControlPlugin::revisionState() are invoked
* from a separate thread to assure that the GUI thread
* won't be blocked. All other methods are invoked in the
* scope of the GUI thread.
*/ */
class LIBDOLPHINPRIVATE_EXPORT RevisionControlPlugin : public QObject class LIBDOLPHINPRIVATE_EXPORT RevisionControlPlugin : public QObject
{ {
@ -49,12 +43,34 @@ class LIBDOLPHINPRIVATE_EXPORT RevisionControlPlugin : public QObject
public: public:
enum RevisionState enum RevisionState
{ {
LocalRevision, /** The file is not under revision control. */
LatestRevision, UnversionedRevision,
/**
* The file is under revision control and represents
* the latest version.
*/
NormalRevision,
/**
* The file is under revision control and a newer
* version exists on the main branch.
*/
UpdateRequiredRevision, UpdateRequiredRevision,
EditingRevision, /**
* The file is under revision control and has been
* modified locally.
*/
LocallyModifiedRevision,
/**
* The file has not been under revision control but
* has been marked to get added with the next commit.
*/
AddedRevision,
/**
* The file is under revision control and has been locally
* modified. A modification has also been done on the main
* branch.
*/
ConflictingRevision ConflictingRevision
// TODO...
}; };
RevisionControlPlugin(); RevisionControlPlugin();
@ -97,14 +113,14 @@ public:
* If an action triggers a change of the revisions, the signal * If an action triggers a change of the revisions, the signal
* RevisionControlPlugin::revisionStatesChanged() must be emitted. * RevisionControlPlugin::revisionStatesChanged() must be emitted.
*/ */
virtual QList<QAction*> contextMenuActions(const KFileItemList& items) const = 0; virtual QList<QAction*> contextMenuActions(const KFileItemList& items) = 0;
/** /**
* Returns the list of actions that should be shown in the context menu * Returns the list of actions that should be shown in the context menu
* for the directory \p directory. If an action triggers a change of the revisions, * for the directory \p directory. If an action triggers a change of the revisions,
* the signal RevisionControlPlugin::revisionStatesChanged() must be emitted. * the signal RevisionControlPlugin::revisionStatesChanged() must be emitted.
*/ */
virtual QList<QAction*> contextMenuActions(const QString& directory) const = 0; virtual QList<QAction*> contextMenuActions(const QString& directory) = 0;
signals: signals:
/** /**
@ -124,11 +140,13 @@ signals:
// TODO: This is just a temporary test class. It will be made available as // TODO: This is just a temporary test class. It will be made available as
// plugin outside Dolphin later. // plugin outside Dolphin later.
#include <QFileInfoList> #include <kfileitem.h>
#include <QHash> #include <QHash>
class LIBDOLPHINPRIVATE_EXPORT SubversionPlugin : public RevisionControlPlugin class LIBDOLPHINPRIVATE_EXPORT SubversionPlugin : public RevisionControlPlugin
{ {
Q_OBJECT
public: public:
SubversionPlugin(); SubversionPlugin();
virtual ~SubversionPlugin(); virtual ~SubversionPlugin();
@ -136,10 +154,23 @@ public:
virtual bool beginRetrieval(const QString& directory); virtual bool beginRetrieval(const QString& directory);
virtual void endRetrieval(); virtual void endRetrieval();
virtual RevisionControlPlugin::RevisionState revisionState(const KFileItem& item); virtual RevisionControlPlugin::RevisionState revisionState(const KFileItem& item);
virtual QList<QAction*> contextMenuActions(const KFileItemList& items) const; virtual QList<QAction*> contextMenuActions(const KFileItemList& items);
virtual QList<QAction*> contextMenuActions(const QString& directory) const; virtual QList<QAction*> contextMenuActions(const QString& directory);
private slots:
void updateFiles();
void showLocalChanges();
void commitFiles();
void addFiles();
void removeFiles();
private: private:
/**
* Executes the command "svn {svnCommand}" for the files that have been
* set by getting the context menu actions (see contextMenuActions()).
*/
void execSvnCommand(const QString& svnCommand);
/** /**
* Returns true, if the content of the local file \p name is equal to the * Returns true, if the content of the local file \p name is equal to the
* content of the revisioned file. * content of the revisioned file.
@ -153,13 +184,16 @@ private:
QDateTime timeStamp; QDateTime timeStamp;
}; };
QString m_directory; QString m_retrievalDir;
QHash<QString, RevisionInfo> m_revisionInfoHash; QHash<QString, RevisionInfo> m_revisionInfoHash;
QAction* m_updateAction; QAction* m_updateAction;
QAction* m_showLocalChangesAction;
QAction* m_commitAction; QAction* m_commitAction;
QAction* m_addAction; QAction* m_addAction;
QAction* m_removeAction; QAction* m_removeAction;
mutable QString m_contextDir;
mutable KFileItemList m_contextItems;
}; };
#endif // REVISIONCONTROLPLUGIN_H #endif // REVISIONCONTROLPLUGIN_H