File indexing completed on 2024-04-21 05:41:04

0001 /*
0002     SPDX-FileCopyrightText: 2009-2011 Peter Penz <peter.penz19@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "fileviewsvnplugin.h"
0008 
0009 #include "fileviewsvnpluginsettings.h"
0010 
0011 #include <KLocalizedString>
0012 #include <KShell>
0013 #include <KPluginFactory>
0014 
0015 #include <QDialog>
0016 #include <QDialogButtonBox>
0017 #include <QDir>
0018 #include <QLabel>
0019 #include <QPlainTextEdit>
0020 #include <QProcess>
0021 #include <QPushButton>
0022 #include <QString>
0023 #include <QStringList>
0024 #include <QTextStream>
0025 #include <QVBoxLayout>
0026 #include <QListWidget>
0027 #include <KConfigGroup>
0028 #include <KWindowConfig>
0029 #include <QWindow>
0030 #include <QTableWidget>
0031 #include <QHeaderView>
0032 
0033 #include "svncommitdialog.h"
0034 #include "svnlogdialog.h"
0035 #include "svncheckoutdialog.h"
0036 #include "svnprogressdialog.h"
0037 #include "svncleanupdialog.h"
0038 
0039 #include "svncommands.h"
0040 
0041 K_PLUGIN_CLASS_WITH_JSON(FileViewSvnPlugin, "fileviewsvnplugin.json")
0042 
0043 FileViewSvnPlugin::FileViewSvnPlugin(QObject* parent, const QList<QVariant>& args) :
0044     KVersionControlPlugin(parent),
0045     m_pendingOperation(false),
0046     m_versionInfoHash(),
0047     m_updateAction(nullptr),
0048     m_showLocalChangesAction(nullptr),
0049     m_commitAction(nullptr),
0050     m_addAction(nullptr),
0051     m_removeAction(nullptr),
0052     m_showUpdatesAction(nullptr),
0053     m_logAction(nullptr),
0054     m_checkoutAction(nullptr),
0055     m_cleanupAction(nullptr),
0056     m_command(),
0057     m_arguments(),
0058     m_errorMsg(),
0059     m_operationCompletedMsg(),
0060     m_contextDir(),
0061     m_contextItems(),
0062     m_process(),
0063     m_tempFile()
0064 {
0065     Q_UNUSED(args);
0066 
0067     m_parentWidget = qobject_cast<QWidget*>(parent);
0068 
0069     m_updateAction = new QAction(this);
0070     m_updateAction->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
0071     m_updateAction->setText(i18nc("@item:inmenu", "SVN Update"));
0072     connect(m_updateAction, &QAction::triggered,
0073             this, &FileViewSvnPlugin::updateFiles);
0074 
0075     m_showLocalChangesAction = new QAction(this);
0076     m_showLocalChangesAction->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right")));
0077     m_showLocalChangesAction->setText(i18nc("@item:inmenu", "Show Local SVN Changes"));
0078     connect(m_showLocalChangesAction, &QAction::triggered,
0079             this, &FileViewSvnPlugin::showLocalChanges);
0080 
0081     m_commitAction = new QAction(this);
0082     m_commitAction->setIcon(QIcon::fromTheme(QStringLiteral("vcs-commit")));
0083     m_commitAction->setText(i18nc("@item:inmenu", "SVN Commit..."));
0084     connect(m_commitAction, &QAction::triggered,
0085             this, &FileViewSvnPlugin::commitDialog);
0086 
0087     m_addAction = new QAction(this);
0088     m_addAction->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0089     m_addAction->setText(i18nc("@item:inmenu", "SVN Add"));
0090     connect(m_addAction, &QAction::triggered,
0091             this, QOverload<>::of(&FileViewSvnPlugin::addFiles));
0092 
0093     m_removeAction = new QAction(this);
0094     m_removeAction->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
0095     m_removeAction->setText(i18nc("@item:inmenu", "SVN Delete"));
0096     connect(m_removeAction, &QAction::triggered,
0097             this, &FileViewSvnPlugin::removeFiles);
0098 
0099     m_revertAction = new QAction(this);
0100     m_revertAction->setIcon(QIcon::fromTheme(QStringLiteral("document-revert")));
0101     m_revertAction->setText(i18nc("@item:inmenu", "SVN Revert"));
0102     connect(m_revertAction, &QAction::triggered,
0103             this, QOverload<>::of(&FileViewSvnPlugin::revertFiles));
0104 
0105     m_showUpdatesAction = new QAction(this);
0106     m_showUpdatesAction->setCheckable(true);
0107     m_showUpdatesAction->setText(i18nc("@item:inmenu", "Show SVN Updates"));
0108     m_showUpdatesAction->setChecked(FileViewSvnPluginSettings::showUpdates());
0109     connect(m_showUpdatesAction, &QAction::toggled,
0110             this, &FileViewSvnPlugin::slotShowUpdatesToggled);
0111     connect(this, &FileViewSvnPlugin::setShowUpdatesChecked,
0112             m_showUpdatesAction, &QAction::setChecked);
0113 
0114     m_logAction = new QAction(this);
0115     m_logAction->setText(i18nc("@action:inmenu", "SVN Log..."));
0116     connect(m_logAction, &QAction::triggered,
0117             this, &FileViewSvnPlugin::logDialog);
0118 
0119     m_checkoutAction = new QAction(this);
0120     m_checkoutAction->setText(i18nc("@action:inmenu", "SVN Checkout..."));
0121     connect(m_checkoutAction, &QAction::triggered,
0122             this, &FileViewSvnPlugin::checkoutDialog);
0123 
0124     m_cleanupAction = new QAction(this);
0125     m_cleanupAction->setText(i18nc("@action:inmenu", "SVN Cleanup..."));
0126     connect(m_cleanupAction, &QAction::triggered,
0127             this, &FileViewSvnPlugin::cleanupDialog);
0128 
0129     connect(&m_process, &QProcess::finished,
0130             this, &FileViewSvnPlugin::slotOperationCompleted);
0131     connect(&m_process, &QProcess::errorOccurred,
0132             this, &FileViewSvnPlugin::slotOperationError);
0133 }
0134 
0135 FileViewSvnPlugin::~FileViewSvnPlugin()
0136 {
0137 }
0138 
0139 QString FileViewSvnPlugin::fileName() const
0140 {
0141     return QLatin1String(".svn");
0142 }
0143 
0144 QString FileViewSvnPlugin::localRepositoryRoot(const QString& directory) const
0145 {
0146     QProcess process;
0147     process.setWorkingDirectory(directory);
0148     process.start(QStringLiteral("svn"), {
0149         QStringLiteral("info"), QStringLiteral("--show-item"), QStringLiteral("wc-root"),
0150     });
0151     if (process.waitForReadyRead(100) && process.exitCode() == 0) {
0152         return QString::fromUtf8(process.readAll().chopped(1));
0153     }
0154     return QString();
0155 }
0156 
0157 bool FileViewSvnPlugin::beginRetrieval(const QString& directory)
0158 {
0159     Q_ASSERT(directory.endsWith(QLatin1Char('/')));
0160 
0161     // Clear all entries for this directory including the entries
0162     // for sub directories
0163     QMutableHashIterator<QString, ItemVersion> it(m_versionInfoHash);
0164     while (it.hasNext()) {
0165         it.next();
0166         // 'svn status' return dirs without trailing slash, so without it we can't remove current
0167         // directory from hash.
0168         if (QString(it.key() + QLatin1Char('/')).startsWith(directory)) {
0169             it.remove();
0170         }
0171     }
0172 
0173     QStringList arguments;
0174     arguments << QLatin1String("status");
0175     if (FileViewSvnPluginSettings::showUpdates()) {
0176         arguments << QLatin1String("--show-updates");
0177     }
0178     arguments << QLatin1String("--no-ignore") << directory;
0179 
0180     QProcess process;
0181     process.start(QLatin1String("svn"), arguments);
0182     while (process.waitForReadyRead()) {
0183         char buffer[1024];
0184         while (process.readLine(buffer, sizeof(buffer)) > 0)  {
0185             ItemVersion version = NormalVersion;
0186             QString filePath = QString::fromLocal8Bit(buffer);
0187 
0188             switch (buffer[0]) {
0189             case 'I':
0190             case '?': version = UnversionedVersion; break;
0191             case 'M': version = LocallyModifiedVersion; break;
0192             case 'A': version = AddedVersion; break;
0193             case 'D': version = RemovedVersion; break;
0194             case 'C': version = ConflictingVersion; break;
0195             case '!': version = MissingVersion; break;
0196             default:
0197                 if (filePath.contains(QLatin1Char('*'))) {
0198                     version = UpdateRequiredVersion;
0199                 } else if (filePath.contains(QLatin1String("W155010"))) {
0200                     version = UnversionedVersion;
0201                 }
0202                 break;
0203             }
0204 
0205             // Only values with a different version as 'NormalVersion'
0206             // are added to the hash table. If a value is not in the
0207             // hash table, it is automatically defined as 'NormalVersion'
0208             // (see FileViewSvnPlugin::itemVersion()).
0209             if (version != NormalVersion) {
0210                 int pos = filePath.indexOf(QLatin1Char('/'));
0211                 const int length = filePath.length() - pos - 1;
0212                 filePath = filePath.mid(pos, length);
0213                 if (!filePath.isEmpty()) {
0214                     m_versionInfoHash.insert(filePath, version);
0215                 }
0216             }
0217         }
0218     }
0219     if ((process.exitCode() != 0 || process.exitStatus() != QProcess::NormalExit)) {
0220         if (FileViewSvnPluginSettings::showUpdates()) {
0221             // Network update failed. Unset ShowUpdates option, which triggers a refresh
0222             Q_EMIT infoMessage(i18nc("@info:status", "SVN status update failed. Disabling Option "
0223                                    "\"Show SVN Updates\"."));
0224             Q_EMIT setShowUpdatesChecked(false);
0225             // this is no fail, we just try again differently
0226             // furthermore returning false shows an error message that would override our info
0227             return true;
0228         } else {
0229             return false;
0230         }
0231     }
0232 
0233     return true;
0234 }
0235 
0236 void FileViewSvnPlugin::endRetrieval()
0237 {
0238     Q_EMIT versionInfoUpdated();
0239 }
0240 
0241 KVersionControlPlugin::ItemVersion FileViewSvnPlugin::itemVersion(const KFileItem& item) const
0242 {
0243     const QString itemUrl = item.localPath();
0244     if (m_versionInfoHash.contains(itemUrl)) {
0245         return m_versionInfoHash.value(itemUrl);
0246     }
0247 
0248     // If parent directory is unversioned item itself is unversioned.
0249     if (isInUnversionedDir(item)) {
0250         return UnversionedVersion;
0251     }
0252 
0253     if (!item.isDir()) {
0254         // files that have not been listed by 'svn status' (= m_versionInfoHash)
0255         // are under version control per definition
0256         // NOTE: svn status does not report files in unversioned paths
0257         const QString path = QFileInfo(itemUrl).path();
0258         return m_versionInfoHash.value(path, NormalVersion);
0259     }
0260 
0261     // The item is a directory. Check whether an item listed by 'svn status' (= m_versionInfoHash)
0262     // is part of this directory. In this case a local modification should be indicated in the
0263     // directory already.
0264     const QString itemDir = itemUrl + QDir::separator();
0265     QHash<QString, ItemVersion>::const_iterator it = m_versionInfoHash.constBegin();
0266     while (it != m_versionInfoHash.constEnd()) {
0267         if (it.key().startsWith(itemDir)) {
0268             const ItemVersion version = m_versionInfoHash.value(it.key());
0269             if (version == LocallyModifiedVersion || version == AddedVersion || version == RemovedVersion) {
0270                 return LocallyModifiedVersion;
0271             }
0272         }
0273         ++it;
0274     }
0275 
0276     return NormalVersion;
0277 }
0278 
0279 QList<QAction*> FileViewSvnPlugin::versionControlActions(const KFileItemList& items) const
0280 {
0281     // Special case: if any item is in unversioned directory we shouldn't add any actions because
0282     // we can do nothing with this item.
0283     for (const auto &i : items) {
0284         if (isInUnversionedDir(i)) {
0285             return {};
0286         }
0287     }
0288 
0289     if (items.count() == 1 && items.first().isDir()) {
0290         return directoryActions(items.first());
0291     }
0292 
0293     for (const KFileItem& item : items) {
0294         m_contextItems.append(item);
0295     }
0296     m_contextDir.clear();
0297 
0298     const bool noPendingOperation = !m_pendingOperation;
0299     if (noPendingOperation) {
0300         // iterate all items and check the version version to know which
0301         // actions can be enabled
0302         const int itemsCount = items.count();
0303         int versionedCount = 0;
0304         int editingCount = 0;
0305         for (const KFileItem& item : items) {
0306             const ItemVersion version = itemVersion(item);
0307             if (version != UnversionedVersion) {
0308                 ++versionedCount;
0309             }
0310 
0311             switch (version) {
0312                 case LocallyModifiedVersion:
0313                 case ConflictingVersion:
0314                 case AddedVersion:
0315                 case RemovedVersion:
0316                     ++editingCount;
0317                     break;
0318                 default:
0319                     break;
0320             }
0321         }
0322         m_commitAction->setEnabled(editingCount > 0);
0323         m_addAction->setEnabled(versionedCount == 0);
0324         m_revertAction->setEnabled(editingCount == itemsCount);
0325         m_removeAction->setEnabled(versionedCount == itemsCount);
0326     } else {
0327         m_commitAction->setEnabled(false);
0328         m_addAction->setEnabled(false);
0329         m_revertAction->setEnabled(false);
0330         m_removeAction->setEnabled(false);
0331     }
0332     m_updateAction->setEnabled(noPendingOperation);
0333 
0334     QList<QAction*> actions;
0335     actions.append(m_updateAction);
0336     actions.append(m_commitAction);
0337     actions.append(m_addAction);
0338     actions.append(m_removeAction);
0339     actions.append(m_revertAction);
0340     actions.append(m_showUpdatesAction);
0341     return actions;
0342 }
0343 
0344 QList<QAction*> FileViewSvnPlugin::outOfVersionControlActions(const KFileItemList& items) const
0345 {
0346     // Only for a single directory.
0347     if (items.count() != 1 || !items.first().isDir()) {
0348         return {};
0349     }
0350 
0351     m_contextDir = items.first().localPath();
0352 
0353     return QList<QAction*>{} << m_checkoutAction;
0354 }
0355 
0356 void FileViewSvnPlugin::updateFiles()
0357 {
0358     SvnProgressDialog *progressDialog = new SvnProgressDialog(i18nc("@title:window", "SVN Update"), m_contextDir, m_parentWidget);
0359     progressDialog->connectToProcess(&m_process);
0360 
0361     execSvnCommand(QLatin1String("update"), QStringList(),
0362                    i18nc("@info:status", "Updating SVN repository..."),
0363                    i18nc("@info:status", "Update of SVN repository failed."),
0364                    i18nc("@info:status", "Updated SVN repository."));
0365 }
0366 
0367 void FileViewSvnPlugin::showLocalChanges()
0368 {
0369     Q_ASSERT(!m_contextDir.isEmpty());
0370     Q_ASSERT(m_contextItems.isEmpty());
0371 
0372     // This temporary file will be deleted on Dolphin close. We make an assumption:
0373     // when the file gets deleted kompare has already loaded it and no longer needs it.
0374     const QString tmpFileNameTemplate = QStringLiteral("%1/%2.XXXXXX").arg(QDir::tempPath(), QDir(m_contextDir).dirName());
0375     QTemporaryFile *file = new QTemporaryFile(tmpFileNameTemplate, this);
0376 
0377     if (!file->open()) {
0378         Q_EMIT errorMessage(i18nc("@info:status", "Could not show local SVN changes."));
0379         return;
0380     }
0381 
0382     QProcess process;
0383     process.setStandardOutputFile(file->fileName());
0384     process.start(
0385         QLatin1String("svn"),
0386         QStringList {
0387             QLatin1String("diff"),
0388             QLatin1String("--git"),
0389             m_contextDir
0390         }
0391     );
0392     if (!process.waitForFinished() || process.exitCode() != 0) {
0393         Q_EMIT errorMessage(i18nc("@info:status", "Could not show local SVN changes: svn diff failed."));
0394         file->deleteLater();
0395         return;
0396     }
0397 
0398     const bool started = QProcess::startDetached(
0399         QLatin1String("kompare"),
0400         QStringList {
0401             file->fileName()
0402         }
0403     );
0404     if (!started) {
0405         Q_EMIT errorMessage(i18nc("@info:status", "Could not show local SVN changes: could not start kompare."));
0406         file->deleteLater();
0407     }
0408 }
0409 
0410 void FileViewSvnPlugin::commitDialog()
0411 {
0412     QStringList context;
0413     if (!m_contextDir.isEmpty()) {
0414         context << m_contextDir;
0415     } else {
0416         for (const auto &i : std::as_const(m_contextItems)) {
0417             context << i.localPath();
0418         }
0419     }
0420 
0421     SvnCommitDialog *svnCommitDialog = new SvnCommitDialog(&m_versionInfoHash, context, m_parentWidget);
0422 
0423     connect(this, &FileViewSvnPlugin::versionInfoUpdated, svnCommitDialog, &SvnCommitDialog::refreshChangesList);
0424 
0425     connect(svnCommitDialog, &SvnCommitDialog::revertFiles, this, QOverload<const QStringList&>::of(&FileViewSvnPlugin::revertFiles));
0426     connect(svnCommitDialog, &SvnCommitDialog::diffFile, this, QOverload<const QString&>::of(&FileViewSvnPlugin::diffFile));
0427     connect(svnCommitDialog, &SvnCommitDialog::addFiles, this, QOverload<const QStringList&>::of(&FileViewSvnPlugin::addFiles));
0428     connect(svnCommitDialog, &SvnCommitDialog::commit, this, &FileViewSvnPlugin::commitFiles);
0429 
0430     svnCommitDialog->setAttribute(Qt::WA_DeleteOnClose);
0431     svnCommitDialog->show();
0432 }
0433 
0434 void FileViewSvnPlugin::addFiles()
0435 {
0436     execSvnCommand(QLatin1String("add"), QStringList(),
0437                    i18nc("@info:status", "Adding files to SVN repository..."),
0438                    i18nc("@info:status", "Adding of files to SVN repository failed."),
0439                    i18nc("@info:status", "Added files to SVN repository."));
0440 }
0441 
0442 void FileViewSvnPlugin::removeFiles()
0443 {
0444     execSvnCommand(QLatin1String("remove"), QStringList(),
0445                    i18nc("@info:status", "Removing files from SVN repository..."),
0446                    i18nc("@info:status", "Removing of files from SVN repository failed."),
0447                    i18nc("@info:status", "Removed files from SVN repository."));
0448 }
0449 
0450 void FileViewSvnPlugin::revertFiles()
0451 {
0452     if (m_contextDir.isEmpty() && m_contextItems.empty()) {
0453         return;
0454     }
0455 
0456     QStringList arguments;
0457     QString root;
0458 
0459     // If we are reverting a directory let's revert everything in it.
0460     if (!m_contextDir.isEmpty()) {
0461         arguments << QLatin1String("--depth") << QLatin1String("infinity");
0462         root = m_contextDir;
0463     } else {
0464         root = SvnCommands::localRoot( m_contextItems.last().localPath() );
0465     }
0466 
0467     SvnProgressDialog *progressDialog = new SvnProgressDialog(i18nc("@title:window", "SVN Revert"), root, m_parentWidget);
0468     progressDialog->connectToProcess(&m_process);
0469 
0470     execSvnCommand(QStringLiteral("revert"), arguments,
0471                    i18nc("@info:status", "Reverting files from SVN repository..."),
0472                    i18nc("@info:status", "Reverting of files from SVN repository failed."),
0473                    i18nc("@info:status", "Reverted files from SVN repository."));
0474 }
0475 
0476 void FileViewSvnPlugin::logDialog()
0477 {
0478     SvnLogDialog *svnLogDialog = new SvnLogDialog(m_contextDir, m_parentWidget);
0479 
0480     connect(svnLogDialog, &SvnLogDialog::errorMessage, this, &FileViewSvnPlugin::errorMessage);
0481     connect(svnLogDialog, &SvnLogDialog::operationCompletedMessage, this, &FileViewSvnPlugin::operationCompletedMessage);
0482     connect(svnLogDialog, &SvnLogDialog::diffAgainstWorkingCopy, this, &FileViewSvnPlugin::diffAgainstWorkingCopy);
0483     connect(svnLogDialog, &SvnLogDialog::diffBetweenRevs, this, &FileViewSvnPlugin::diffBetweenRevs);
0484 
0485     svnLogDialog->setAttribute(Qt::WA_DeleteOnClose);
0486     svnLogDialog->show();
0487 }
0488 
0489 void FileViewSvnPlugin::checkoutDialog()
0490 {
0491     SvnCheckoutDialog *svnCheckoutDialog = new SvnCheckoutDialog(m_contextDir, m_parentWidget);
0492 
0493     connect(svnCheckoutDialog, &SvnCheckoutDialog::infoMessage, this, &FileViewSvnPlugin::infoMessage);
0494     connect(svnCheckoutDialog, &SvnCheckoutDialog::errorMessage, this, &FileViewSvnPlugin::errorMessage);
0495     connect(svnCheckoutDialog, &SvnCheckoutDialog::operationCompletedMessage, this, &FileViewSvnPlugin::operationCompletedMessage);
0496 
0497     svnCheckoutDialog->setAttribute(Qt::WA_DeleteOnClose);
0498     svnCheckoutDialog->show();
0499 }
0500 
0501 void FileViewSvnPlugin::cleanupDialog()
0502 {
0503     SvnCleanupDialog *svnCleanupDialog = new SvnCleanupDialog(m_contextDir, m_parentWidget);
0504 
0505     connect(svnCleanupDialog, &SvnCleanupDialog::errorMessage, this, &FileViewSvnPlugin::errorMessage);
0506     connect(svnCleanupDialog, &SvnCleanupDialog::operationCompletedMessage, this, &FileViewSvnPlugin::operationCompletedMessage);
0507 }
0508 
0509 void FileViewSvnPlugin::slotOperationCompleted(int exitCode, QProcess::ExitStatus exitStatus)
0510 {
0511     m_pendingOperation = false;
0512 
0513     if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) {
0514         Q_EMIT errorMessage(m_errorMsg);
0515     } else if (m_contextItems.isEmpty()) {
0516         Q_EMIT operationCompletedMessage(m_operationCompletedMsg);
0517         Q_EMIT itemVersionsChanged();
0518     } else {
0519         startSvnCommandProcess();
0520     }
0521 }
0522 
0523 void FileViewSvnPlugin::slotOperationError()
0524 {
0525     // don't do any operation on other items anymore
0526     m_contextItems.clear();
0527     m_pendingOperation = false;
0528 
0529     Q_EMIT errorMessage(m_errorMsg);
0530 }
0531 
0532 void FileViewSvnPlugin::slotShowUpdatesToggled(bool checked)
0533 {
0534     FileViewSvnPluginSettings* settings = FileViewSvnPluginSettings::self();
0535     Q_ASSERT(settings != nullptr);
0536     settings->setShowUpdates(checked);
0537     settings->save();
0538 
0539     Q_EMIT itemVersionsChanged();
0540 }
0541 
0542 void FileViewSvnPlugin::revertFiles(const QStringList& filesPath)
0543 {
0544     if (filesPath.empty()) {
0545         return;
0546     }
0547 
0548     for (const auto &i : std::as_const(filesPath)) {
0549         m_contextItems.append(KFileItem(QUrl::fromLocalFile(i)));
0550     }
0551     m_contextDir.clear();
0552 
0553     SvnProgressDialog *progressDialog = new SvnProgressDialog(i18nc("@title:window", "SVN Revert"), SvnCommands::localRoot(filesPath.first()), m_parentWidget);
0554     progressDialog->connectToProcess(&m_process);
0555 
0556     execSvnCommand(QLatin1String("revert"), QStringList() << filesPath,
0557                    i18nc("@info:status", "Reverting changes to file..."),
0558                    i18nc("@info:status", "Revert file failed."),
0559                    i18nc("@info:status", "File reverted."));
0560 }
0561 
0562 void FileViewSvnPlugin::diffFile(const QString& filePath)
0563 {
0564     // For a diff we will export last known file local revision from a remote and compare. We will
0565     // not use basic SVN action 'svn diff --extensions -U<lines> <fileName>' because we should count
0566     // lines or set maximum number for this.
0567     // With a maximum number (2147483647) 'svn diff' starts to work slowly.
0568 
0569     diffAgainstWorkingCopy(filePath, SvnCommands::localRevision(filePath));
0570 }
0571 
0572 void FileViewSvnPlugin::diffAgainstWorkingCopy(const QString& localFilePath, ulong rev)
0573 {
0574     QTemporaryFile *file = new QTemporaryFile(this);
0575     if (!SvnCommands::exportFile(QUrl::fromLocalFile(localFilePath), rev, file)) {
0576         Q_EMIT errorMessage(i18nc("@info:status", "Could not show local SVN changes for a file: could not get file."));
0577         file->deleteLater();
0578         return;
0579     }
0580 
0581     const bool started = QProcess::startDetached(
0582         QLatin1String("kompare"),
0583         QStringList {
0584             file->fileName(),
0585             localFilePath
0586         }
0587     );
0588     if (!started) {
0589         Q_EMIT errorMessage(i18nc("@info:status", "Could not show local SVN changes: could not start kompare."));
0590         file->deleteLater();
0591     }
0592 }
0593 
0594 void FileViewSvnPlugin::diffBetweenRevs(const QString& remoteFilePath, ulong rev1, ulong rev2)
0595 {
0596     QTemporaryFile *file1 = new QTemporaryFile(this);
0597     QTemporaryFile *file2 = new QTemporaryFile(this);
0598     if (!SvnCommands::exportFile(QUrl::fromLocalFile(remoteFilePath), rev1, file1)) {
0599         Q_EMIT errorMessage(i18nc("@info:status", "Could not show local SVN changes for a file: could not get file."));
0600         file1->deleteLater();
0601         return;
0602     }
0603     if (!SvnCommands::exportFile(QUrl::fromLocalFile(remoteFilePath), rev2, file2)) {
0604         Q_EMIT errorMessage(i18nc("@info:status", "Could not show local SVN changes for a file: could not get file."));
0605         file1->deleteLater();
0606         file2->deleteLater();
0607         return;
0608     }
0609 
0610     const bool started = QProcess::startDetached(
0611         QLatin1String("kompare"),
0612         QStringList {
0613             file2->fileName(),
0614             file1->fileName()
0615         }
0616     );
0617     if (!started) {
0618         Q_EMIT errorMessage(i18nc("@info:status", "Could not show local SVN changes: could not start kompare."));
0619         file1->deleteLater();
0620         file2->deleteLater();
0621     }
0622 }
0623 
0624 void FileViewSvnPlugin::addFiles(const QStringList& filesPath)
0625 {
0626     for (const auto &i : std::as_const(filesPath)) {
0627         m_contextItems.append(KFileItem(QUrl::fromLocalFile(i)));
0628     }
0629     m_contextDir.clear();
0630 
0631     addFiles();
0632 }
0633 
0634 void FileViewSvnPlugin::commitFiles(const QStringList& context, const QString& msg)
0635 {
0636     if (context.empty()) {
0637         return;
0638     }
0639 
0640     // Write the commit description into a temporary file, so
0641     // that it can be read by the command "svn commit -F". The temporary
0642     // file must stay alive until slotOperationCompleted() is invoked and will
0643     // be destroyed when the version plugin is destructed.
0644     if (!m_tempFile.open())  {
0645         Q_EMIT errorMessage(i18nc("@info:status", "Commit of SVN changes failed."));
0646         return;
0647     }
0648 
0649     QTextStream out(&m_tempFile);
0650     const QString fileName = m_tempFile.fileName();
0651     out << msg;
0652     m_tempFile.close();
0653 
0654     QStringList arguments;
0655     arguments << context << QStringLiteral("-F") << fileName;
0656 
0657     // Lets clear m_contextDir and m_contextItems variables: we will pass everything in arguments.
0658     // This is needed because startSvnCommandProcess() uses only one QString for svn transaction at
0659     // a time but we want to commit everything.
0660     m_contextDir.clear();
0661     m_contextItems.clear();
0662 
0663     SvnProgressDialog *progressDialog = new SvnProgressDialog(i18nc("@title:window", "SVN Commit"), SvnCommands::localRoot(context.first()), m_parentWidget);
0664     progressDialog->connectToProcess(&m_process);
0665 
0666     execSvnCommand(QLatin1String("commit"), arguments,
0667                    i18nc("@info:status", "Committing SVN changes..."),
0668                    i18nc("@info:status", "Commit of SVN changes failed."),
0669                    i18nc("@info:status", "Committed SVN changes."));
0670 }
0671 
0672 void FileViewSvnPlugin::execSvnCommand(const QString& svnCommand,
0673                                        const QStringList& arguments,
0674                                        const QString& infoMsg,
0675                                        const QString& errorMsg,
0676                                        const QString& operationCompletedMsg)
0677 {
0678     Q_EMIT infoMessage(infoMsg);
0679 
0680     m_command = svnCommand;
0681     m_arguments = arguments;
0682     m_errorMsg = errorMsg;
0683     m_operationCompletedMsg = operationCompletedMsg;
0684 
0685     startSvnCommandProcess();
0686 }
0687 
0688 void FileViewSvnPlugin::startSvnCommandProcess()
0689 {
0690     Q_ASSERT(m_process.state() == QProcess::NotRunning);
0691     m_pendingOperation = true;
0692 
0693     const QString program(QLatin1String("svn"));
0694     QStringList arguments;
0695     arguments << m_command << m_arguments;
0696     if (!m_contextDir.isEmpty()) {
0697         arguments << m_contextDir;
0698         m_contextDir.clear();
0699     } else {
0700         // If m_contextDir is empty and m_contextItems is empty then all svn arguments are in
0701         // m_arguments (for example see commitFiles()).
0702         if (!m_contextItems.isEmpty()) {
0703             const KFileItem item = m_contextItems.takeLast();
0704             arguments << item.localPath();
0705             // the remaining items of m_contextItems will be executed
0706             // after the process has finished (see slotOperationFinished())
0707         }
0708     }
0709     m_process.start(program, arguments);
0710 }
0711 
0712 QList<QAction*> FileViewSvnPlugin::directoryActions(const KFileItem& directory) const
0713 {
0714     m_contextDir = directory.localPath();
0715     if (!m_contextDir.endsWith(QLatin1Char('/'))) {
0716         m_contextDir += QLatin1Char('/');
0717     }
0718     m_contextItems.clear();
0719 
0720     // Only enable the SVN actions if no SVN commands are
0721     // executed currently (see slotOperationCompleted() and
0722     // startSvnCommandProcess()).
0723     const bool enabled = !m_pendingOperation;
0724     m_updateAction->setEnabled(enabled);
0725 
0726     const ItemVersion version = itemVersion(directory);
0727     m_showLocalChangesAction->setEnabled(enabled && (version != NormalVersion));
0728     m_addAction->setEnabled(enabled && (version == UnversionedVersion));
0729     m_removeAction->setEnabled(enabled && (version == NormalVersion));
0730     if (version == LocallyModifiedVersion || version == AddedVersion || version == RemovedVersion) {
0731         m_commitAction->setEnabled(enabled);
0732         m_revertAction->setEnabled(enabled);
0733     } else {
0734         m_commitAction->setEnabled(false);
0735         m_revertAction->setEnabled(false);
0736     }
0737 
0738     QList<QAction*> actions;
0739     actions.append(m_updateAction);
0740     actions.append(m_showLocalChangesAction);
0741     actions.append(m_commitAction);
0742     actions.append(m_showUpdatesAction);
0743     actions.append(m_addAction);
0744     actions.append(m_removeAction);
0745     actions.append(m_revertAction);
0746     actions.append(m_logAction);
0747     actions.append(m_cleanupAction);
0748     return actions;
0749 }
0750 
0751 bool FileViewSvnPlugin::isInUnversionedDir(const KFileItem& item) const
0752 {
0753     const QString itemPath = item.localPath();
0754 
0755     for (auto it = m_versionInfoHash.cbegin(); it != m_versionInfoHash.cend(); ++it) {
0756         // Add QDir::separator() to m_versionInfoHash entry to ensure this is a directory.
0757         if (it.value() == UnversionedVersion && itemPath.startsWith(it.key() + QDir::separator())) {
0758             return true;
0759         }
0760     }
0761 
0762     return false;
0763 }
0764 
0765 #include "fileviewsvnplugin.moc"
0766 
0767 #include "moc_fileviewsvnplugin.cpp"