1
/***************************************************************************
2
* Copyright (C) 2009 by Peter Penz <peter.penz@gmx.at> *
4
* This program is free software; you can redistribute it and/or modify *
5
* it under the terms of the GNU General Public License as published by *
6
* the Free Software Foundation; either version 2 of the License, or *
7
* (at your option) any later version. *
9
* This program is distributed in the hope that it will be useful, *
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12
* GNU General Public License for more details. *
14
* You should have received a copy of the GNU General Public License *
15
* along with this program; if not, write to the *
16
* Free Software Foundation, Inc., *
17
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18
***************************************************************************/
20
#include "fileviewsvnplugin.h"
23
#include <kdemacros.h>
25
#include <kfileitem.h>
35
#include <QStringList>
37
#include <QTextStream>
39
#include <KPluginFactory>
40
#include <KPluginLoader>
41
K_PLUGIN_FACTORY(FileViewSvnPluginFactory, registerPlugin<FileViewSvnPlugin>();)
42
K_EXPORT_PLUGIN(FileViewSvnPluginFactory("fileviewsvnplugin"))
44
FileViewSvnPlugin::FileViewSvnPlugin(QObject* parent, const QList<QVariant>& args) :
45
KVersionControlPlugin(parent),
49
m_showLocalChangesAction(0),
55
m_operationCompletedMsg(),
62
m_updateAction = new KAction(this);
63
m_updateAction->setIcon(KIcon("view-refresh"));
64
m_updateAction->setText(i18nc("@item:inmenu", "SVN Update"));
65
connect(m_updateAction, SIGNAL(triggered()),
66
this, SLOT(updateFiles()));
68
m_showLocalChangesAction = new KAction(this);
69
m_showLocalChangesAction->setIcon(KIcon("view-split-left-right"));
70
m_showLocalChangesAction->setText(i18nc("@item:inmenu", "Show Local SVN Changes"));
71
connect(m_showLocalChangesAction, SIGNAL(triggered()),
72
this, SLOT(showLocalChanges()));
74
m_commitAction = new KAction(this);
75
m_commitAction->setText(i18nc("@item:inmenu", "SVN Commit..."));
76
connect(m_commitAction, SIGNAL(triggered()),
77
this, SLOT(commitFiles()));
79
m_addAction = new KAction(this);
80
m_addAction->setIcon(KIcon("list-add"));
81
m_addAction->setText(i18nc("@item:inmenu", "SVN Add"));
82
connect(m_addAction, SIGNAL(triggered()),
83
this, SLOT(addFiles()));
85
m_removeAction = new KAction(this);
86
m_removeAction->setIcon(KIcon("list-remove"));
87
m_removeAction->setText(i18nc("@item:inmenu", "SVN Delete"));
88
connect(m_removeAction, SIGNAL(triggered()),
89
this, SLOT(removeFiles()));
92
FileViewSvnPlugin::~FileViewSvnPlugin()
96
QString FileViewSvnPlugin::fileName() const
101
bool FileViewSvnPlugin::beginRetrieval(const QString& directory)
103
Q_ASSERT(directory.endsWith('/'));
105
QStringList arguments;
106
arguments << "status" << "--show-updates" << directory;
109
process.start("svn", arguments);
110
while (process.waitForReadyRead()) {
112
while (process.readLine(buffer, sizeof(buffer)) > 0) {
113
VersionState state = NormalVersion;
114
QString filePath(buffer);
117
case '?': state = UnversionedVersion; break;
118
case 'M': state = LocallyModifiedVersion; break;
119
case 'A': state = AddedVersion; break;
120
case 'D': state = RemovedVersion; break;
121
case 'C': state = ConflictingVersion; break;
123
if (filePath.contains('*')) {
124
state = UpdateRequiredVersion;
129
int pos = filePath.indexOf('/');
130
const int length = filePath.length() - pos - 1;
131
filePath = filePath.mid(pos, length);
132
if (!filePath.isEmpty()) {
133
m_versionInfoHash.insert(filePath, state);
138
m_versionInfoKeys = m_versionInfoHash.keys();
142
void FileViewSvnPlugin::endRetrieval()
146
KVersionControlPlugin::VersionState FileViewSvnPlugin::versionState(const KFileItem& item)
148
const QString itemUrl = item.localPath();
149
if (m_versionInfoHash.contains(itemUrl)) {
150
return m_versionInfoHash.value(itemUrl);
154
// files that have not been listed by 'svn status' (= m_versionInfoHash)
155
// are under version control per definition
156
return NormalVersion;
159
// The item is a directory. Check whether an item listed by 'svn status' (= m_versionInfoHash)
160
// is part of this directory. In this case a local modification should be indicated in the
161
// directory already.
162
foreach (const QString& key, m_versionInfoKeys) {
163
if (key.startsWith(itemUrl)) {
164
const VersionState state = m_versionInfoHash.value(key);
165
if (state == LocallyModifiedVersion) {
166
return LocallyModifiedVersion;
171
return NormalVersion;
174
QList<QAction*> FileViewSvnPlugin::contextMenuActions(const KFileItemList& items)
176
Q_ASSERT(!items.isEmpty());
177
foreach (const KFileItem& item, items) {
178
m_contextItems.append(item);
180
m_contextDir.clear();
182
// iterate all items and check the version state to know which
183
// actions can be enabled
184
const int itemsCount = items.count();
185
int versionedCount = 0;
186
int editingCount = 0;
187
foreach (const KFileItem& item, items) {
188
const VersionState state = versionState(item);
189
if (state != UnversionedVersion) {
194
case LocallyModifiedVersion:
195
case ConflictingVersion:
202
m_commitAction->setEnabled(editingCount > 0);
203
m_addAction->setEnabled(versionedCount == 0);
204
m_removeAction->setEnabled(versionedCount == itemsCount);
206
QList<QAction*> actions;
207
actions.append(m_updateAction);
208
actions.append(m_commitAction);
209
actions.append(m_addAction);
210
actions.append(m_removeAction);
214
QList<QAction*> FileViewSvnPlugin::contextMenuActions(const QString& directory)
216
const bool enabled = m_contextItems.isEmpty();
218
m_contextDir = directory;
221
// Only enable the SVN actions if no SVN commands are
222
// executed currently (see slotOperationCompleted() and
223
// startSvnCommandProcess()).
224
m_updateAction->setEnabled(enabled);
225
m_showLocalChangesAction->setEnabled(enabled);
226
m_commitAction->setEnabled(enabled);
228
QList<QAction*> actions;
229
actions.append(m_updateAction);
230
actions.append(m_showLocalChangesAction);
231
actions.append(m_commitAction);
235
void FileViewSvnPlugin::updateFiles()
237
execSvnCommand("update",
238
i18nc("@info:status", "Updating SVN repository..."),
239
i18nc("@info:status", "Update of SVN repository failed."),
240
i18nc("@info:status", "Updated SVN repository."));
243
void FileViewSvnPlugin::showLocalChanges()
245
Q_ASSERT(!m_contextDir.isEmpty());
246
Q_ASSERT(m_contextItems.isEmpty());
248
const QString command = "mkfifo /tmp/fifo; svn diff " +
249
KShell::quoteArg(m_contextDir) +
250
" > /tmp/fifo & kompare /tmp/fifo; rm /tmp/fifo";
251
KRun::runCommand(command, 0);
254
void FileViewSvnPlugin::commitFiles()
256
KDialog dialog(0, Qt::Dialog);
258
KVBox* box = new KVBox(&dialog);
259
new QLabel(i18nc("@label", "Description:"), box);
260
QTextEdit* editor = new QTextEdit(box);
262
dialog.setMainWidget(box);
263
dialog.setCaption(i18nc("@title:window", "SVN Commit"));
264
dialog.setButtons(KDialog::Ok | KDialog::Cancel);
265
dialog.setDefaultButton(KDialog::Ok);
266
dialog.setButtonText(KDialog::Ok, i18nc("@action:button", "Commit"));
268
KConfigGroup dialogConfig(KSharedConfig::openConfig("dolphinrc"),
270
dialog.restoreDialogSize(dialogConfig);
272
if (dialog.exec() == QDialog::Accepted) {
273
// Write the commit description into a temporary file, so
274
// that it can be read by the command "svn commit -F". The temporary
275
// file must stay alive until slotOperationCompleted() is invoked and will
276
// be destroyed when the version plugin is destructed.
277
if (!m_tempFile.open()) {
278
emit errorMessage(i18nc("@info:status", "Commit of SVN changes failed."));
282
QTextStream out(&m_tempFile);
283
const QString fileName = m_tempFile.fileName();
284
out << editor->toPlainText();
287
execSvnCommand("commit -F " + KShell::quoteArg(fileName),
288
i18nc("@info:status", "Committing SVN changes..."),
289
i18nc("@info:status", "Commit of SVN changes failed."),
290
i18nc("@info:status", "Committed SVN changes."));
293
dialog.saveDialogSize(dialogConfig, KConfigBase::Persistent);
296
void FileViewSvnPlugin::addFiles()
298
execSvnCommand("add",
299
i18nc("@info:status", "Adding files to SVN repository..."),
300
i18nc("@info:status", "Adding of files to SVN repository failed."),
301
i18nc("@info:status", "Added files to SVN repository."));
304
void FileViewSvnPlugin::removeFiles()
306
execSvnCommand("remove",
307
i18nc("@info:status", "Removing files from SVN repository..."),
308
i18nc("@info:status", "Removing of files from SVN repository failed."),
309
i18nc("@info:status", "Removed files from SVN repository."));
312
void FileViewSvnPlugin::slotOperationCompleted(int exitCode, QProcess::ExitStatus exitStatus)
314
if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) {
315
emit errorMessage(m_errorMsg);
316
} else if (m_contextItems.isEmpty()) {
317
emit operationCompletedMessage(m_operationCompletedMsg);
318
emit versionStatesChanged();
320
startSvnCommandProcess();
324
void FileViewSvnPlugin::slotOperationError()
326
emit errorMessage(m_errorMsg);
328
// don't do any operation on other items anymore
329
m_contextItems.clear();
332
void FileViewSvnPlugin::execSvnCommand(const QString& svnCommand,
333
const QString& infoMsg,
334
const QString& errorMsg,
335
const QString& operationCompletedMsg)
337
emit infoMessage(infoMsg);
339
m_command = svnCommand;
340
m_errorMsg = errorMsg;
341
m_operationCompletedMsg = operationCompletedMsg;
343
startSvnCommandProcess();
346
void FileViewSvnPlugin::startSvnCommandProcess()
348
QProcess* process = new QProcess(this);
349
connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
350
this, SLOT(slotOperationCompleted(int, QProcess::ExitStatus)));
351
connect(process, SIGNAL(error(QProcess::ProcessError)),
352
this, SLOT(slotOperationError()));
354
const QString program = "svn " + m_command + ' ';
355
if (!m_contextDir.isEmpty()) {
356
process->start(program + KShell::quoteArg(m_contextDir));
357
m_contextDir.clear();
359
const KFileItem item = m_contextItems.takeLast();
360
process->start(program + KShell::quoteArg(item.localPath()));
361
// the remaining items of m_contextItems will be executed
362
// after the process has finished (see slotOperationFinished())