File indexing completed on 2024-04-28 05:42:04

0001 /***************************************************************************
0002  *   Copyright (C) 2005-2009 by Rajko Albrecht                             *
0003  *   ral@alwins-world.de                                                   *
0004  *   Copyright (C) 2023 by Łukasz Wojniłowicz                              *
0005  *   lukasz.wojnilowicz@gmail.com                                          *
0006  *                                                                         *
0007  *   This program is free software; you can redistribute it and/or modify  *
0008  *   it under the terms of the GNU General Public License as published by  *
0009  *   the Free Software Foundation; either version 2 of the License, or     *
0010  *   (at your option) any later version.                                   *
0011  *                                                                         *
0012  *   This program is distributed in the hope that it will be useful,       *
0013  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0014  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0015  *   GNU General Public License for more details.                          *
0016  *                                                                         *
0017  *   You should have received a copy of the GNU General Public License     *
0018  *   along with this program; if not, write to the                         *
0019  *   Free Software Foundation, Inc.,                                       *
0020  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
0021  ***************************************************************************/
0022 #include "svnactions.h"
0023 #include "blamedisplay.h"
0024 #include "ccontextlistener.h"
0025 #include "checkoutinfo_impl.h"
0026 #include "fillcachethread.h"
0027 #include "graphtree/revisiontree.h"
0028 #include "graphtree/revtreewidget.h"
0029 #include "itemdisplay.h"
0030 #include "ksvnwidgets/commitmsg_impl.h"
0031 #include "ksvnwidgets/diffbrowser.h"
0032 #include "ksvnwidgets/encodingselector_impl.h"
0033 #include "ksvnwidgets/models/commitmodelhelper.h"
0034 #include "ksvnwidgets/revertform.h"
0035 #include "modifiedthread.h"
0036 #include "propertiesdlg.h"
0037 #include "rangeinput_impl.h"
0038 #include "settings/kdesvnsettings.h"
0039 #include "stopdlg.h"
0040 #include "svnitem.h"
0041 #include "svnlogdlgimp.h"
0042 #include "svnqt/annotate_line.h"
0043 #include "svnqt/cache/LogCache.h"
0044 #include "svnqt/cache/ReposConfig.h"
0045 #include "svnqt/cache/ReposLog.h"
0046 #include "svnqt/client.h"
0047 #include "svnqt/client_annotate_parameter.h"
0048 #include "svnqt/client_commit_parameter.h"
0049 #include "svnqt/client_parameter.h"
0050 #include "svnqt/client_update_parameter.h"
0051 #include "svnqt/context_listener.h"
0052 #include "svnqt/dirent.h"
0053 #include "svnqt/svnqt_defines.h"
0054 #include "svnqt/svnqttypes.h"
0055 #include "svnqt/targets.h"
0056 #include "svnqt/url.h"
0057 #include "tcontextlistener.h"
0058 
0059 #include "fronthelpers/watchedprocess.h"
0060 
0061 #include "cacheentry.h"
0062 #include "fronthelpers/cursorstack.h"
0063 #include "helpers/kdesvn_debug.h"
0064 #include "helpers/ktranslateurl.h"
0065 #include "helpers/stringhelper.h"
0066 #include "helpers/windowgeometryhelper.h"
0067 
0068 #include <KApplicationTrader>
0069 #include <KIO/ApplicationLauncherJob>
0070 #include <KMessageBox>
0071 #include <KMessageBox_KDESvnCompat>
0072 #include <kconfig.h>
0073 #include <klocalizedstring.h>
0074 
0075 #include <QApplication>
0076 #include <QDesktopServices>
0077 #include <QFileInfo>
0078 #include <QFontDatabase>
0079 #include <QInputDialog>
0080 #include <QMap>
0081 #include <QMimeDatabase>
0082 #include <QReadWriteLock>
0083 #include <QString>
0084 #include <QTemporaryDir>
0085 #include <QTemporaryFile>
0086 #include <QTimer>
0087 #include <QTreeWidget>
0088 #include <QTreeWidgetItem>
0089 
0090 #include <sys/time.h>
0091 #include <unistd.h>
0092 
0093 /// @todo has to be removed for a real fix of ticket #613
0094 #include <svn_config.h>
0095 // wait not longer than 10 seconds for a thread
0096 #define MAX_THREAD_WAITTIME 10000
0097 
0098 class SvnActionsData
0099 {
0100 public:
0101     SvnActionsData()
0102         : m_ParentList(nullptr)
0103         , m_SvnContextListener(nullptr)
0104         , m_Svnclient(svn::Client::getobject(svn::ContextP()))
0105         , runblocked(false)
0106     {
0107     }
0108 
0109     ~SvnActionsData()
0110     {
0111         cleanDialogs();
0112         delete m_SvnContextListener;
0113     }
0114 
0115     static bool isExternalDiff()
0116     {
0117         if (Kdesvnsettings::use_external_diff()) {
0118             const QString edisp = Kdesvnsettings::external_diff_display();
0119             const QVector<QStringRef> wlist = edisp.splitRef(QLatin1Char(' '));
0120             if (wlist.count() >= 3 && edisp.contains(QLatin1String("%1")) && edisp.contains(QLatin1String("%2"))) {
0121                 return true;
0122             }
0123         }
0124         return false;
0125     }
0126 
0127     void clearCaches()
0128     {
0129         QWriteLocker wl(&(m_InfoCacheLock));
0130         m_PropertiesCache.clear();
0131         m_contextData.clear();
0132         m_InfoCache.clear();
0133     }
0134 
0135     void cleanDialogs()
0136     {
0137         if (m_DiffDialog) {
0138             delete m_DiffDialog;
0139             m_DiffDialog = nullptr;
0140         }
0141         if (m_LogDialog) {
0142             m_LogDialog->saveSize();
0143             delete m_LogDialog;
0144             m_LogDialog = nullptr;
0145         }
0146     }
0147 
0148     /// @todo set some standards options to svn::Context. This should made via a Config class in svnqt (future release 1.4)
0149     /// this is a workaround for ticket #613
0150     void setStandards()
0151     {
0152         if (!m_CurrentContext) {
0153             return;
0154         }
0155         svn_config_t *cfg_config = static_cast<svn_config_t *>(apr_hash_get(m_CurrentContext->ctx()->config, SVN_CONFIG_CATEGORY_CONFIG, APR_HASH_KEY_STRING));
0156         if (!cfg_config) {
0157             return;
0158         }
0159         svn_config_set(cfg_config, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF_CMD, nullptr);
0160     }
0161 
0162     ItemDisplay *m_ParentList;
0163 
0164     CContextListener *m_SvnContextListener;
0165     svn::ContextP m_CurrentContext;
0166     svn::ClientP m_Svnclient;
0167 
0168     helpers::statusCache m_UpdateCache;
0169     helpers::statusCache m_Cache;
0170     helpers::statusCache m_conflictCache;
0171     helpers::statusCache m_repoLockCache;
0172     helpers::itemCache<svn::PathPropertiesMapListPtr> m_PropertiesCache;
0173     /// \todo as persistent cache (sqlite?)
0174     helpers::itemCache<svn::InfoEntry> m_InfoCache;
0175     helpers::itemCache<QVariant> m_MergeInfoCache;
0176 
0177     QPointer<DiffBrowser> m_DiffBrowserPtr;
0178     QPointer<KSvnSimpleOkDialog> m_DiffDialog;
0179     QPointer<SvnLogDlgImp> m_LogDialog;
0180 
0181     QMap<QString, QString> m_contextData;
0182     QReadWriteLock m_InfoCacheLock;
0183 
0184     bool runblocked;
0185 };
0186 
0187 #define EMIT_FINISHED emit sendNotify(i18n("Finished"))
0188 #define EMIT_REFRESH emit sigRefreshAll()
0189 #define DIALOGS_SIZES "display_dialogs_sizes"
0190 
0191 SvnActions::SvnActions(ItemDisplay *parent, bool processes_blocked)
0192     : QObject(parent ? parent->realWidget() : nullptr)
0193     , SimpleLogCb()
0194     , m_CThread(nullptr)
0195     , m_UThread(nullptr)
0196     , m_FCThread(nullptr)
0197 {
0198     m_Data.reset(new SvnActionsData);
0199     m_Data->m_ParentList = parent;
0200     m_Data->m_SvnContextListener = new CContextListener(this);
0201     m_Data->runblocked = processes_blocked;
0202     connect(m_Data->m_SvnContextListener, &CContextListener::sendNotify, this, &SvnActions::slotNotifyMessage);
0203 }
0204 
0205 svn::ClientP SvnActions::svnclient()
0206 {
0207     return m_Data->m_Svnclient;
0208 }
0209 
0210 SvnActions::~SvnActions()
0211 {
0212     killallThreads();
0213 }
0214 
0215 void SvnActions::slotNotifyMessage(const QString &aMsg)
0216 {
0217     emit sendNotify(aMsg);
0218 }
0219 
0220 void SvnActions::reInitClient()
0221 {
0222     m_Data->clearCaches();
0223     m_Data->cleanDialogs();
0224     if (m_Data->m_CurrentContext) {
0225         m_Data->m_CurrentContext->setListener(nullptr);
0226     }
0227     m_Data->m_CurrentContext = svn::ContextP(new svn::Context);
0228     m_Data->m_CurrentContext->setListener(m_Data->m_SvnContextListener);
0229     m_Data->m_Svnclient->setContext(m_Data->m_CurrentContext);
0230     ///@todo workaround has to be replaced
0231     m_Data->setStandards();
0232 }
0233 
0234 void SvnActions::makeLog(const svn::Revision &start,
0235                          const svn::Revision &end,
0236                          const svn::Revision &peg,
0237                          const QString &which,
0238                          bool follow,
0239                          bool list_files,
0240                          int limit)
0241 {
0242     svn::LogEntriesMapPtr logs = getLog(start, end, peg, which, list_files, limit, follow);
0243     if (!logs) {
0244         return;
0245     }
0246     svn::InfoEntry info;
0247     if (!singleInfo(which, peg, info)) {
0248         return;
0249     }
0250     const QString reposRoot = info.reposRoot().toString();
0251     bool need_modal = m_Data->runblocked || QApplication::activeModalWidget() != nullptr;
0252     if (need_modal || !m_Data->m_LogDialog) {
0253         m_Data->m_LogDialog = new SvnLogDlgImp(this, need_modal);
0254         connect(m_Data->m_LogDialog,
0255                 &SvnLogDlgImp::makeDiff,
0256                 this,
0257                 QOverload<const QString &, const svn::Revision &, const QString &, const svn::Revision &, QWidget *>::of(&SvnActions::makeDiff));
0258         connect(m_Data->m_LogDialog, &SvnLogDlgImp::makeCat, this, &SvnActions::slotMakeCat);
0259     }
0260 
0261     if (m_Data->m_LogDialog) {
0262         m_Data->m_LogDialog->dispLog(logs,
0263                                      info.url().toString().mid(reposRoot.length()),
0264                                      reposRoot,
0265                                      (peg == svn::Revision::UNDEFINED ? (svn::Url::isValid(which) ? svn::Revision::HEAD : svn::Revision::UNDEFINED) : peg),
0266                                      which);
0267         if (need_modal) {
0268             m_Data->m_LogDialog->exec();
0269             m_Data->m_LogDialog->saveSize();
0270             delete m_Data->m_LogDialog;
0271         } else {
0272             m_Data->m_LogDialog->show();
0273             m_Data->m_LogDialog->raise();
0274         }
0275     }
0276     EMIT_FINISHED;
0277 }
0278 
0279 svn::LogEntriesMapPtr SvnActions::getLog(const svn::Revision &start,
0280                                          const svn::Revision &end,
0281                                          const svn::Revision &peg,
0282                                          const QString &which,
0283                                          bool list_files,
0284                                          int limit,
0285                                          QWidget *parent)
0286 {
0287     return getLog(start, end, peg, which, list_files, limit, Kdesvnsettings::log_follows_nodes(), parent);
0288 }
0289 
0290 svn::LogEntriesMapPtr SvnActions::getLog(const svn::Revision &start,
0291                                          const svn::Revision &end,
0292                                          const svn::Revision &peg,
0293                                          const QString &which,
0294                                          bool list_files,
0295                                          int limit,
0296                                          bool follow,
0297                                          QWidget *parent)
0298 {
0299     svn::LogEntriesMapPtr logs;
0300     if (!m_Data->m_CurrentContext) {
0301         return logs;
0302     }
0303 
0304     bool mergeinfo = hasMergeInfo(m_Data->m_ParentList->baseUri().isEmpty() ? which : m_Data->m_ParentList->baseUri());
0305 
0306     svn::LogParameter params;
0307     params.targets(which)
0308         .revisionRange(start, end)
0309         .peg(peg)
0310         .includeMergedRevisions(mergeinfo)
0311         .limit(limit)
0312         .discoverChangedPathes(list_files)
0313         .strictNodeHistory(!follow);
0314 
0315     try {
0316         StopDlg sdlg(m_Data->m_SvnContextListener,
0317                      (parent ? parent : m_Data->m_ParentList->realWidget()),
0318                      i18nc("@title:window", "Logs"),
0319                      i18n("Getting logs - hit Cancel for abort"));
0320         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
0321         logs = svn::LogEntriesMapPtr(new svn::LogEntriesMap);
0322         if (doNetworking()) {
0323             if (!m_Data->m_Svnclient->log(params, *logs)) {
0324                 logs.clear();
0325                 return logs;
0326             }
0327         } else {
0328             svn::InfoEntry e;
0329             if (!singleInfo(m_Data->m_ParentList->baseUri(), svn::Revision::BASE, e)) {
0330                 logs.clear();
0331                 return logs;
0332             }
0333             if (svn::Url::isLocal(e.reposRoot().toString())) {
0334                 if (!m_Data->m_Svnclient->log(params, *logs)) {
0335                     logs.clear();
0336                     return logs;
0337                 }
0338             } else {
0339                 svn::cache::ReposLog rl(m_Data->m_Svnclient, e.reposRoot().toString());
0340                 QString what;
0341                 const QString s1 = e.url().toString().mid(e.reposRoot().toString().length());
0342                 if (which == QLatin1String(".")) {
0343                     what = s1;
0344                 } else {
0345                     const QString s2 = which.mid(m_Data->m_ParentList->baseUri().length());
0346                     what = s1 + QLatin1Char('/') + s2;
0347                 }
0348                 rl.log(what, start, end, peg, *logs, !follow, limit);
0349             }
0350         }
0351     } catch (const svn::Exception &e) {
0352         emit clientException(e.msg());
0353         logs.clear();
0354     }
0355     if (logs && logs->isEmpty()) {
0356         logs.clear();
0357         emit clientException(i18n("Got no logs"));
0358     }
0359     return logs;
0360 }
0361 
0362 bool SvnActions::getSingleLog(svn::LogEntry &t, const svn::Revision &r, const QString &what, const svn::Revision &peg, QString &root)
0363 {
0364     bool res = false;
0365 
0366     if (what.isEmpty()) {
0367         return res;
0368     }
0369     if (root.isEmpty()) {
0370         svn::InfoEntry inf;
0371         if (!singleInfo(what, peg, inf)) {
0372             return res;
0373         }
0374         root = inf.reposRoot().toString();
0375     }
0376 
0377     if (!svn::Url::isLocal(root)) {
0378         svn::LogEntriesMap _m;
0379         try {
0380             svn::cache::ReposLog rl(m_Data->m_Svnclient, root);
0381             if (rl.isValid() && rl.simpleLog(_m, r, r, true)) {
0382                 const svn::LogEntriesMap::const_iterator it = _m.constFind(r.revnum());
0383                 if (it != _m.constEnd()) {
0384                     t = it.value();
0385                     res = true;
0386                 }
0387             }
0388         } catch (const svn::Exception &e) {
0389             emit clientException(e.msg());
0390         }
0391     }
0392 
0393     if (!res) {
0394         svn::LogEntriesMapPtr log = getLog(r, r, peg, root, true, 1);
0395         if (log) {
0396             const svn::LogEntriesMap::const_iterator it = log->constFind(r.revnum());
0397             if (it != log->constEnd()) {
0398                 t = it.value();
0399                 res = true;
0400             }
0401         }
0402     }
0403     return res;
0404 }
0405 
0406 bool SvnActions::hasMergeInfo(const QString &originpath)
0407 {
0408     QVariant _m(false);
0409     QString path;
0410 
0411     svn::InfoEntry e;
0412     if (!singleInfo(originpath, svn::Revision::UNDEFINED, e)) {
0413         return false;
0414     }
0415     path = e.reposRoot().toString();
0416     if (!m_Data->m_MergeInfoCache.findSingleValid(path, _m)) {
0417         bool mergeinfo;
0418         try {
0419             mergeinfo = m_Data->m_Svnclient->RepoHasCapability(path, svn::CapabilityMergeinfo);
0420         } catch (const svn::ClientException &e) {
0421             emit sendNotify(e.msg());
0422             return false;
0423         }
0424         _m.setValue(mergeinfo);
0425         m_Data->m_MergeInfoCache.insertKey(_m, path);
0426     }
0427     return _m.toBool();
0428 }
0429 
0430 bool SvnActions::singleInfo(const QString &what, const svn::Revision &_rev, svn::InfoEntry &target, const svn::Revision &_peg)
0431 {
0432     QString url;
0433     QString cacheKey;
0434     svn::Revision peg = _peg;
0435     if (!m_Data->m_CurrentContext) {
0436         return false;
0437     }
0438 #ifdef DEBUG_TIMER
0439     QTime _counttime;
0440     _counttime.start();
0441 #endif
0442 
0443     if (!svn::Url::isValid(what)) {
0444         // working copy
0445         // url = svn::Wc::getUrl(what);
0446         url = what;
0447         if (_rev != svn::Revision::WORKING && url.contains(QLatin1Char('@'))) {
0448             url += QStringLiteral("@BASE");
0449         }
0450         peg = svn::Revision::UNDEFINED;
0451         cacheKey = url;
0452     } else {
0453         // valid url
0454         QUrl _uri(what);
0455         QString prot = svn::Url::transformProtokoll(_uri.scheme());
0456         _uri.setScheme(prot);
0457         url = _uri.toString();
0458         if (peg == svn::Revision::UNDEFINED) {
0459             peg = _rev;
0460         }
0461         if (peg == svn::Revision::UNDEFINED) {
0462             peg = svn::Revision::HEAD;
0463         }
0464         cacheKey = _rev.toString() + QLatin1Char('/') + url;
0465     }
0466     svn::InfoEntries e;
0467     bool must_write = false;
0468 
0469     {
0470         QReadLocker rl(&(m_Data->m_InfoCacheLock));
0471         if (cacheKey.isEmpty() || !m_Data->m_InfoCache.findSingleValid(cacheKey, target)) {
0472             must_write = true;
0473             try {
0474                 e = (m_Data->m_Svnclient->info(url, svn::DepthEmpty, _rev, peg));
0475             } catch (const svn::Exception &ce) {
0476                 qCDebug(KDESVN_LOG) << "single info: " << ce.msg() << Qt::endl;
0477                 emit clientException(ce.msg());
0478                 return false;
0479             }
0480             if (e.isEmpty() || e[0].reposRoot().isEmpty()) {
0481                 emit clientException(i18n("Got no info."));
0482                 return false;
0483             }
0484             target = e[0];
0485         }
0486     }
0487     if (must_write) {
0488         QWriteLocker wl(&(m_Data->m_InfoCacheLock));
0489         if (!cacheKey.isEmpty()) {
0490             m_Data->m_InfoCache.insertKey(e[0], cacheKey);
0491             if (peg != svn::Revision::UNDEFINED && peg.kind() != svn::Revision::NUMBER && peg.kind() != svn::Revision::DATE) {
0492                 // for persistent storage, store head into persistent cache makes no sense.
0493                 cacheKey = e[0].revision().toString() + QLatin1Char('/') + url;
0494                 m_Data->m_InfoCache.insertKey(e[0], cacheKey);
0495             }
0496         }
0497     }
0498 #ifdef DEBUG_TIMER
0499     qCDebug(KDESVN_LOG) << "Time getting info for " << cacheKey << ": " << _counttime.elapsed();
0500 #endif
0501 
0502     return true;
0503 }
0504 
0505 void SvnActions::makeTree(const QString &what, const svn::Revision &_rev, const svn::Revision &startr, const svn::Revision &endr)
0506 {
0507     svn::InfoEntry info;
0508     if (!singleInfo(what, _rev, info)) {
0509         return;
0510     }
0511     const QString reposRoot = info.reposRoot().toString();
0512 
0513     if (Kdesvnsettings::fill_cache_on_tree()) {
0514         stopFillCache();
0515     }
0516 
0517     QPointer<KSvnSimpleOkDialog> dlg(new KSvnSimpleOkDialog(QStringLiteral("revisiontree_dlg"), m_Data->m_ParentList->realWidget()));
0518     dlg->setWindowTitle(i18nc("@title:window", "History of %1", info.url().toString().mid(reposRoot.length())));
0519 
0520     RevisionTree *rt(
0521         new RevisionTree(m_Data->m_Svnclient, m_Data->m_SvnContextListener, reposRoot, startr, endr, info.url().toString().mid(reposRoot.length()), _rev, dlg));
0522     if (rt->isValid()) {
0523         RevTreeWidget *disp = rt->getView();
0524         if (disp) {
0525             dlg->addWidget(disp);
0526             connect(disp, &RevTreeWidget::makeNorecDiff, this, &SvnActions::makeNorecDiff);
0527             connect(disp,
0528                     &RevTreeWidget::makeRecDiff,
0529                     this,
0530                     QOverload<const QString &, const svn::Revision &, const QString &, const svn::Revision &, QWidget *>::of(&SvnActions::makeDiff));
0531             connect(disp, &RevTreeWidget::makeCat, this, &SvnActions::slotMakeCat);
0532             dlg->exec();
0533         }
0534     }
0535     delete dlg;
0536 }
0537 
0538 void SvnActions::makeBlame(const svn::Revision &start, const svn::Revision &end, SvnItem *k)
0539 {
0540     if (k) {
0541         makeBlame(start, end, k->fullName(), m_Data->m_ParentList->realWidget());
0542     }
0543 }
0544 
0545 void SvnActions::makeBlame(const svn::Revision &start, const svn::Revision &end, const QString &k, QWidget *_p, const svn::Revision &_peg, SimpleLogCb *_acb)
0546 {
0547     if (!m_Data->m_CurrentContext) {
0548         return;
0549     }
0550     svn::AnnotatedFile blame;
0551     QWidget *_parent = _p ? _p : m_Data->m_ParentList->realWidget();
0552     bool mergeinfo = hasMergeInfo(m_Data->m_ParentList->baseUri().isEmpty() ? k : m_Data->m_ParentList->baseUri());
0553 
0554     svn::AnnotateParameter params;
0555     params.path(k).pegRevision(_peg == svn::Revision::UNDEFINED ? end : _peg).revisionRange(svn::RevisionRange(start, end)).includeMerged(mergeinfo);
0556 
0557     try {
0558         CursorStack a(Qt::BusyCursor);
0559         StopDlg sdlg(m_Data->m_SvnContextListener, _parent, i18nc("@title:window", "Annotate"), i18n("Annotate lines - hit Cancel for abort"));
0560         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
0561         m_Data->m_Svnclient->annotate(blame, params);
0562     } catch (const svn::Exception &e) {
0563         emit clientException(e.msg());
0564         return;
0565     }
0566     if (blame.isEmpty()) {
0567         QString ex = i18n("Got no annotate");
0568         emit clientException(ex);
0569         return;
0570     }
0571     EMIT_FINISHED;
0572     BlameDisplay::displayBlame(_acb ? _acb : this, k, blame, _p);
0573 }
0574 
0575 bool SvnActions::makeGet(const svn::Revision &start, const QString &what, const QString &target, const svn::Revision &peg, QWidget *_dlgparent)
0576 {
0577     if (!m_Data->m_CurrentContext) {
0578         return false;
0579     }
0580     CursorStack a(Qt::BusyCursor);
0581     QWidget *dlgp = _dlgparent ? _dlgparent : m_Data->m_ParentList->realWidget();
0582     svn::Path p(what);
0583     try {
0584         StopDlg sdlg(m_Data->m_SvnContextListener, dlgp, i18nc("@title:window", "Content Get"), i18n("Getting content - hit Cancel for abort"));
0585         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
0586         m_Data->m_Svnclient->get(p, target, start, peg);
0587     } catch (const svn::Exception &e) {
0588         emit clientException(e.msg());
0589         return false;
0590     } catch (...) {
0591         QString ex = i18n("Error getting content");
0592         emit clientException(ex);
0593         return false;
0594     }
0595     return true;
0596 }
0597 
0598 void SvnActions::slotMakeCat(const svn::Revision &start, const QString &what, const QString &disp, const svn::Revision &peg, QWidget *_dlgparent)
0599 {
0600     QTemporaryFile content;
0601     content.setAutoRemove(true);
0602     // required otherwise it will not generate a unique name...
0603     if (!content.open()) {
0604         emit clientException(i18n("Error while open temporary file"));
0605         return;
0606     }
0607     QString tname = content.fileName();
0608     content.close();
0609     QWidget *parent = _dlgparent ? _dlgparent : m_Data->m_ParentList->realWidget();
0610 
0611     if (!makeGet(start, what, tname, peg, parent)) {
0612         return;
0613     }
0614     EMIT_FINISHED;
0615     QMimeDatabase db;
0616     const QMimeType mimeType(db.mimeTypeForFile(tname));
0617 
0618     const auto service = KApplicationTrader::preferredService(mimeType.name());
0619 
0620     if (service) {
0621         content.setAutoRemove(false);
0622         auto *job = new KIO::ApplicationLauncherJob(service);
0623         job->setUrls({QUrl::fromLocalFile(tname)});
0624         job->setRunFlags(KIO::ApplicationLauncherJob::DeleteTemporaryFiles);
0625         job->start();
0626         return;
0627     }
0628 
0629     QFile file(tname);
0630     file.open(QIODevice::ReadOnly);
0631     const QByteArray co = file.readAll();
0632 
0633     if (!co.isEmpty()) {
0634         QPointer<KSvnSimpleOkDialog> dlg = new KSvnSimpleOkDialog(QStringLiteral("cat_display_dlg"), parent);
0635         dlg->setWindowTitle(i18nc("@title:window", "Content of %1", disp));
0636         QTextBrowser *ptr = new QTextBrowser(dlg);
0637         ptr->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
0638         ptr->setWordWrapMode(QTextOption::NoWrap);
0639         ptr->setReadOnly(true);
0640         ptr->setText(QString::fromUtf8(co, co.size()));
0641         dlg->addWidget(ptr);
0642         dlg->exec();
0643         delete dlg;
0644     } else {
0645         KMessageBox::information(parent, i18n("Got no content."));
0646     }
0647 }
0648 
0649 bool SvnActions::makeMkdir(const svn::Targets &targets, const QString &logMessage)
0650 {
0651     if (!m_Data->m_CurrentContext || targets.targets().isEmpty()) {
0652         return false;
0653     }
0654     try {
0655         m_Data->m_Svnclient->mkdir(targets, logMessage);
0656     } catch (const svn::Exception &e) {
0657         emit clientException(e.msg());
0658         return false;
0659     }
0660     return true;
0661 }
0662 
0663 QString SvnActions::makeMkdir(const QString &parentDir)
0664 {
0665     if (!m_Data->m_CurrentContext) {
0666         return QString();
0667     }
0668     bool isOk = false;
0669     const QString ex =
0670         QInputDialog::getText(m_Data->m_ParentList->realWidget(), i18n("New folder"), i18n("Enter folder name:"), QLineEdit::Normal, QString(), &isOk);
0671     if (!isOk || ex.isEmpty()) {
0672         return QString();
0673     }
0674     svn::Path target(parentDir);
0675     target.addComponent(ex);
0676 
0677     try {
0678         m_Data->m_Svnclient->mkdir(target, QString());
0679     } catch (const svn::Exception &e) {
0680         emit clientException(e.msg());
0681         return QString();
0682     }
0683 
0684     return target.path();
0685 }
0686 
0687 QString SvnActions::getInfo(const SvnItemList &lst, const svn::Revision &rev, const svn::Revision &peg, bool recursive, bool all)
0688 {
0689     QString res;
0690     for (const SvnItem *item : lst) {
0691         if (all) {
0692             res += QStringLiteral("<h4 align=\"center\">%1</h4>").arg(item->fullName());
0693         }
0694         res += getInfo(item->fullName(), rev, peg, recursive, all);
0695     }
0696     return res;
0697 }
0698 
0699 QString SvnActions::getInfo(const QString &_what, const svn::Revision &rev, const svn::Revision &peg, bool recursive, bool all)
0700 {
0701     if (!m_Data->m_CurrentContext) {
0702         return QString();
0703     }
0704     svn::InfoEntries entries;
0705     if (recursive) {
0706         try {
0707             StopDlg sdlg(m_Data->m_SvnContextListener,
0708                          m_Data->m_ParentList->realWidget(),
0709                          i18nc("@title:window", "Details"),
0710                          i18n("Retrieving information - hit Cancel for abort"));
0711             connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
0712             QString path = _what;
0713             if (_what.contains(QLatin1Char('@')) && !svn::Url::isValid(_what)) {
0714                 path += QLatin1String("@BASE");
0715             }
0716             entries = (m_Data->m_Svnclient->info(path, recursive ? svn::DepthInfinity : svn::DepthEmpty, rev, peg));
0717         } catch (const svn::Exception &e) {
0718             emit clientException(e.msg());
0719             return QString();
0720         }
0721     } else {
0722         svn::InfoEntry info;
0723         if (!singleInfo(_what, rev, info, peg)) {
0724             return QString();
0725         }
0726         entries.append(info);
0727     }
0728     return getInfo(entries, _what, all);
0729 }
0730 
0731 QString SvnActions::getInfo(const svn::InfoEntries &entries, const QString &_what, bool all)
0732 {
0733     QString text;
0734     static QString rb(QStringLiteral("<tr><td><nobr>"));
0735     static QString re(QStringLiteral("</nobr></td></tr>\n"));
0736     static QString cs(QStringLiteral("</nobr>:</td><td><nobr>"));
0737     unsigned int val = 0;
0738     for (const svn::InfoEntry &entry : entries) {
0739         if (val > 0) {
0740             text += QStringLiteral("<hline>");
0741         }
0742         ++val;
0743         text += QStringLiteral("<p align=\"center\">");
0744         text += QStringLiteral("<table cellspacing=0 cellpadding=0>");
0745         if (!entry.Name().isEmpty()) {
0746             text += rb + i18n("Name") + cs + (entry.Name()) + re;
0747         }
0748         if (all) {
0749             text += rb + i18n("URL") + cs + (entry.url().toDisplayString()) + re;
0750             if (!entry.reposRoot().toString().isEmpty()) {
0751                 text += rb + i18n("Canonical repository URL") + cs + (entry.reposRoot().toDisplayString()) + re;
0752             }
0753             if (!entry.checksum().isEmpty()) {
0754                 text += rb + i18n("Checksum") + cs + (entry.checksum()) + re;
0755             }
0756         }
0757         text += rb + i18n("Type") + cs;
0758         switch (entry.kind()) {
0759         case svn_node_none:
0760             text += i18n("Absent");
0761             break;
0762         case svn_node_file:
0763             text += i18n("File");
0764             break;
0765         case svn_node_dir:
0766             text += i18n("Folder");
0767             break;
0768         case svn_node_unknown:
0769         default:
0770             text += i18n("Unknown");
0771             break;
0772         }
0773         text += re;
0774         if (entry.kind() == svn_node_file) {
0775             text += rb + i18n("Size") + cs;
0776             if (entry.size() != svn::InfoEntry::SVNQT_SIZE_UNKNOWN) {
0777                 text += helpers::ByteToString(entry.size());
0778             } else if (entry.working_size() != svn::InfoEntry::SVNQT_SIZE_UNKNOWN) {
0779                 text += helpers::ByteToString(entry.working_size());
0780             }
0781             text += re;
0782         }
0783         if (all) {
0784             text += rb + i18n("Schedule") + cs;
0785             switch (entry.Schedule()) {
0786             case svn_wc_schedule_normal:
0787                 text += i18n("Normal");
0788                 break;
0789             case svn_wc_schedule_add:
0790                 text += i18n("Addition");
0791                 break;
0792             case svn_wc_schedule_delete:
0793                 text += i18n("Deletion");
0794                 break;
0795             case svn_wc_schedule_replace:
0796                 text += i18n("Replace");
0797                 break;
0798             default:
0799                 text += i18n("Unknown");
0800                 break;
0801             }
0802             text += re;
0803             text += rb + i18n("UUID") + cs + (entry.uuid()) + re;
0804         }
0805         text += rb + i18n("Last author") + cs + (entry.cmtAuthor()) + re;
0806         if (entry.cmtDate().IsValid()) {
0807             text += rb + i18n("Last committed") + cs + entry.cmtDate().toString() + re;
0808         }
0809         text += rb + i18n("Last revision") + cs + entry.cmtRev().toString() + re;
0810         if (entry.textTime().IsValid()) {
0811             text += rb + i18n("Content last changed") + cs + entry.textTime().toString() + re;
0812         }
0813         if (all) {
0814             if (entry.propTime().IsValid()) {
0815                 text += rb + i18n("Property last changed") + cs + entry.propTime().toString() + re;
0816             }
0817             for (const auto &_cfi : entry.conflicts()) {
0818                 text += rb + i18n("New version of conflicted file") + cs + (_cfi->theirFile());
0819             }
0820             if (entry.prejfile().length()) {
0821                 text += rb + i18n("Property reject file") + cs + (entry.prejfile()) + re;
0822             }
0823 
0824             if (!entry.copyfromUrl().isEmpty()) {
0825                 text += rb + i18n("Copy from URL") + cs + (entry.copyfromUrl().toDisplayString()) + re;
0826             }
0827             if (entry.lockEntry().Locked()) {
0828                 text += rb + i18n("Lock token") + cs + (entry.lockEntry().Token()) + re;
0829                 text += rb + i18n("Owner") + cs + (entry.lockEntry().Owner()) + re;
0830                 text += rb + i18n("Locked on") + cs + entry.lockEntry().Date().toString() + re;
0831                 text += rb + i18n("Lock comment") + cs + entry.lockEntry().Comment() + re;
0832             } else {
0833                 svn::StatusPtr d;
0834                 if (checkReposLockCache(_what, d) && d && d->lockEntry().Locked()) {
0835                     text += rb + i18n("Lock token") + cs + (d->lockEntry().Token()) + re;
0836                     text += rb + i18n("Owner") + cs + (d->lockEntry().Owner()) + re;
0837                     text += rb + i18n("Locked on") + cs + d->lockEntry().Date().toString() + re;
0838                     text += rb + i18n("Lock comment") + cs + d->lockEntry().Comment() + re;
0839                 }
0840             }
0841         }
0842         text += QStringLiteral("</table></p>\n");
0843     }
0844     return text;
0845 }
0846 
0847 void SvnActions::makeInfo(const SvnItemList &lst, const svn::Revision &rev, const svn::Revision &peg, bool recursive)
0848 {
0849     QStringList infoList;
0850     infoList.reserve(lst.size());
0851     for (const auto item : lst) {
0852         const QString text = getInfo(item->fullName(), rev, peg, recursive, true);
0853         if (!text.isEmpty()) {
0854             infoList += text;
0855         }
0856     }
0857     showInfo(infoList);
0858 }
0859 
0860 void SvnActions::makeInfo(const QStringList &lst, const svn::Revision &rev, const svn::Revision &peg, bool recursive)
0861 {
0862     QStringList infoList;
0863     infoList.reserve(lst.size());
0864     for (const QString &l : lst) {
0865         const QString text = getInfo(l, rev, peg, recursive, true);
0866         if (!text.isEmpty()) {
0867             infoList += text;
0868         }
0869     }
0870     showInfo(infoList);
0871 }
0872 
0873 void SvnActions::showInfo(const QStringList &infoList)
0874 {
0875     if (infoList.isEmpty()) {
0876         return;
0877     }
0878     QString text(QLatin1String("<html><head></head><body>"));
0879     for (const QString &info : infoList) {
0880         text += QLatin1String("<h4 align=\"center\">") + info + QLatin1String("</h4>");
0881     }
0882     text += QLatin1String("</body></html>");
0883 
0884     QPointer<KSvnSimpleOkDialog> dlg = new KSvnSimpleOkDialog(QStringLiteral("info_dialog"), QApplication::activeModalWidget());
0885     dlg->setWindowTitle(i18nc("@title:window", "Infolist"));
0886     QTextBrowser *ptr = new QTextBrowser(dlg);
0887     dlg->addWidget(ptr);
0888     ptr->setReadOnly(true);
0889     ptr->setText(text);
0890     dlg->exec();
0891     delete dlg;
0892 }
0893 
0894 void SvnActions::editProperties(SvnItem *k, const svn::Revision &rev)
0895 {
0896     if (!m_Data->m_CurrentContext) {
0897         return;
0898     }
0899     if (!k) {
0900         return;
0901     }
0902     QPointer<PropertiesDlg> dlg(new PropertiesDlg(k, svnclient(), rev));
0903     connect(dlg, SIGNAL(clientException(QString)), m_Data->m_ParentList->realWidget(), SLOT(slotClientException(QString)));
0904     if (dlg->exec() != QDialog::Accepted) {
0905         delete dlg;
0906         return;
0907     }
0908     svn::PropertiesMap setList;
0909     QStringList delList;
0910     dlg->changedItems(setList, delList);
0911     changeProperties(setList, delList, k->fullName());
0912     k->refreshStatus();
0913     EMIT_FINISHED;
0914     delete dlg;
0915 }
0916 
0917 bool SvnActions::changeProperties(const svn::PropertiesMap &setList, const QStringList &delList, const QString &path, const svn::Depth &depth)
0918 {
0919     try {
0920         svn::PropertiesParameter params;
0921         params.path(path).depth(depth);
0922         StopDlg sdlg(m_Data->m_SvnContextListener,
0923                      m_Data->m_ParentList->realWidget(),
0924                      i18nc("@title:window", "Applying Properties"),
0925                      i18n("<center>Applying<br/>hit cancel for abort</center>"));
0926         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
0927         // propertyValue == QString::null -> delete property
0928         for (int pos = 0; pos < delList.size(); ++pos) {
0929             m_Data->m_Svnclient->propset(params.propertyName(delList.at(pos)));
0930         }
0931         for (svn::PropertiesMap::ConstIterator it = setList.begin(); it != setList.end(); ++it) {
0932             m_Data->m_Svnclient->propset(params.propertyName(it.key()).propertyValue(it.value()));
0933         }
0934     } catch (const svn::Exception &e) {
0935         emit clientException(e.msg());
0936         return false;
0937     }
0938     return true;
0939 }
0940 
0941 /*!
0942     \fn SvnActions::slotCommit()
0943  */
0944 void SvnActions::doCommit(const SvnItemList &which)
0945 {
0946     if (!m_Data->m_CurrentContext || !m_Data->m_ParentList->isWorkingCopy()) {
0947         return;
0948     }
0949 
0950     svn::Paths targets;
0951     if (which.isEmpty()) {
0952         targets.push_back(svn::Path(QStringLiteral(".")));
0953     } else {
0954         targets.reserve(which.size());
0955         for (const SvnItem *item : which) {
0956             targets.push_back(svn::Path(m_Data->m_ParentList->relativePath(item)));
0957         }
0958     }
0959     if (!m_Data->m_ParentList->baseUri().isEmpty()) {
0960         if (!QDir::setCurrent(m_Data->m_ParentList->baseUri())) {
0961             QString msg = i18n("Could not change to folder %1\n", m_Data->m_ParentList->baseUri()) + QString::fromLocal8Bit(strerror(errno));
0962             emit sendNotify(msg);
0963         }
0964     }
0965     if (makeCommit(svn::Targets(targets)) && Kdesvnsettings::log_cache_on_open()) {
0966         startFillCache(m_Data->m_ParentList->baseUri(), true);
0967     }
0968 }
0969 
0970 bool SvnActions::makeCommit(const svn::Targets &targets)
0971 {
0972     bool ok, keeplocks;
0973     svn::Depth depth;
0974     svn::Revision nnum;
0975     bool review = Kdesvnsettings::review_commit();
0976     QString msg;
0977 
0978     if (!doNetworking()) {
0979         emit clientException(i18n("Not commit because networking is disabled"));
0980         return false;
0981     }
0982 
0983     svn::CommitParameter commit_parameters;
0984     stopFillCache();
0985     if (!review) {
0986         msg = Commitmsg_impl::getLogmessage(&ok, &depth, &keeplocks, m_Data->m_ParentList->realWidget());
0987         if (!ok) {
0988             return false;
0989         }
0990         commit_parameters.targets(targets);
0991     } else {
0992         CommitActionEntries _check, _uncheck, _result;
0993         svn::StatusEntries _Cache;
0994         depth = svn::DepthEmpty;
0995         QVector<QString> unversionedDirectories;
0996         svn::StatusParameter params;
0997         params.depth(svn::DepthInfinity).all(false).update(false).noIgnore(false).revision(svn::Revision::HEAD);
0998         /// @todo filter out double entries
0999         for (const auto &tgt : targets.targets()) {
1000             try {
1001                 StopDlg sdlg(m_Data->m_SvnContextListener,
1002                              m_Data->m_ParentList->realWidget(),
1003                              i18nc("@title:window", "Status / List"),
1004                              i18n("Creating list / check status"));
1005 
1006                 /* Add unversioned directories by 'svn add' for 'svn status'
1007                  * to be able to list unversioned files in them.
1008                  */
1009                 bool containsUnversionedDirectory;
1010                 do {
1011                     containsUnversionedDirectory = false;
1012                     _Cache = m_Data->m_Svnclient->status(params.path(tgt.path()));
1013                     for (const svn::StatusPtr &ptr : qAsConst(_Cache)) {
1014                         if (!ptr->isVersioned()) {
1015                             if (QFileInfo(ptr->entry().name()).isDir()) {
1016                                 m_Data->m_Svnclient->add(ptr->path(), svn::DepthEmpty, false, false, true);
1017                                 unversionedDirectories.append(ptr->path());
1018                                 containsUnversionedDirectory = true;
1019                             }
1020                         }
1021                     }
1022                 } while (containsUnversionedDirectory);
1023 
1024             } catch (const svn::Exception &e) {
1025                 emit clientException(e.msg());
1026                 return false;
1027             }
1028             for (const svn::StatusPtr &ptr : qAsConst(_Cache)) {
1029                 const QString _p = ptr->path();
1030                 QVector<svn_wc_status_kind> nodeStatusesForCheck{svn_wc_status_modified,
1031                                                                  svn_wc_status_added,
1032                                                                  svn_wc_status_replaced,
1033                                                                  svn_wc_status_deleted,
1034                                                                  svn_wc_status_modified};
1035                 // check the node status, not the text status (it does not cover the prop status)
1036                 if (ptr->isRealVersioned() && nodeStatusesForCheck.contains(ptr->nodeStatus())) {
1037                     if (ptr->nodeStatus() == svn_wc_status_deleted) {
1038                         _check.append(CommitActionEntry(_p, i18n("Delete"), CommitActionEntry::DELETE));
1039                     } else if (unversionedDirectories.contains(ptr->path())) {
1040                         /* Display "Add and Commit" (for the user),
1041                          * but commit only, since we've already added the directory.
1042                          */
1043                         _uncheck.append(CommitActionEntry(_p, i18n("Add and Commit"), CommitActionEntry::COMMIT));
1044                     } else {
1045                         _check.append(CommitActionEntry(_p, i18n("Commit"), CommitActionEntry::COMMIT));
1046                     }
1047                 } else if (ptr->nodeStatus() == svn_wc_status_missing) {
1048                     _uncheck.append(CommitActionEntry(_p, i18n("Delete and Commit"), CommitActionEntry::MISSING_DELETE));
1049                 } else if (!ptr->isVersioned()) {
1050                     _uncheck.append(CommitActionEntry(_p, i18n("Add and Commit"), CommitActionEntry::ADD_COMMIT));
1051                 }
1052             }
1053         }
1054         msg = Commitmsg_impl::getLogmessage(_check, _uncheck, this, _result, &ok, &keeplocks, m_Data->m_ParentList->realWidget());
1055 
1056         for (auto it = unversionedDirectories.crbegin(); it != unversionedDirectories.crend(); ++it) {
1057             bool unversionedDirectoryAlreadyChecked = false;
1058             for (auto i = 0; i < _result.size(); ++i) {
1059                 // Avoid partial matching of directory names.
1060                 QRegularExpression re(*it + "(/|$)");
1061                 if (re.match(_result.at(i).name()).hasMatch()) {
1062                     for (auto j = i; j < _result.size(); ++j) {
1063                         if (*it == _result.at(j).name()) {
1064                             unversionedDirectoryAlreadyChecked = true;
1065                             break;
1066                         }
1067                     }
1068                     if (!unversionedDirectoryAlreadyChecked) {
1069                         /* Add unversioned and unchecked directories of unversioned and checked files,
1070                          * to prevent svn::Client:commit from reporting that they're missing.
1071                          */
1072                         _result.append(CommitActionEntry(*it, i18n("Add and Commit"), CommitActionEntry::COMMIT));
1073                         unversionedDirectoryAlreadyChecked = true;
1074                         break;
1075                     }
1076                 }
1077             }
1078             if (!unversionedDirectoryAlreadyChecked || !ok) {
1079                 /* Remove unversioned and unchecked directories by 'svn remove'
1080                  * so that they aren't commited by mistake.
1081                  */
1082                 m_Data->m_Svnclient->remove(*it, false, true, svn::PropertiesMap());
1083             }
1084         }
1085 
1086         if (!ok || _result.isEmpty()) {
1087             return false;
1088         }
1089         svn::Paths _add, _commit, _delete;
1090         depth = svn::DepthInfinity;
1091         for (const CommitActionEntry &entry : qAsConst(_result)) {
1092             _commit.append(entry.name());
1093             if (entry.type() == CommitActionEntry::ADD_COMMIT) {
1094                 _add.append(entry.name());
1095             } else if (entry.type() == CommitActionEntry::MISSING_DELETE) {
1096                 _delete.append(entry.name());
1097             }
1098         }
1099         if (!_add.isEmpty()) {
1100             if (!addItems(_add, svn::DepthEmpty)) {
1101                 return false;
1102             }
1103         }
1104         if (!_delete.isEmpty()) {
1105             makeDelete(svn::Targets(_delete));
1106         }
1107         commit_parameters.targets(svn::Targets(_commit));
1108     }
1109     commit_parameters.keepLocks(keeplocks).depth(depth).message(msg);
1110     try {
1111         StopDlg sdlg(m_Data->m_SvnContextListener, m_Data->m_ParentList->realWidget(), i18nc("@title:window", "Commit"), i18n("Commit - hit Cancel for abort"));
1112         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
1113         nnum = m_Data->m_Svnclient->commit(commit_parameters);
1114     } catch (const svn::Exception &e) {
1115         emit clientException(e.msg());
1116         return false;
1117     }
1118     EMIT_REFRESH;
1119     emit sendNotify(i18n("Committed revision %1.", nnum.toString()));
1120     return true;
1121 }
1122 
1123 void SvnActions::slotProcessDataRead(const QByteArray &data, WatchedProcess *)
1124 {
1125     emit sendNotify(QString::fromLocal8Bit(data));
1126 }
1127 
1128 bool SvnActions::get(const QString &what, const QString &to, const svn::Revision &rev, const svn::Revision &peg, QWidget *p)
1129 {
1130     svn::Revision _peg = peg;
1131     if (_peg == svn::Revision::UNDEFINED) {
1132         _peg = rev;
1133     }
1134 
1135     try {
1136         StopDlg sdlg(m_Data->m_SvnContextListener,
1137                      p ? p : m_Data->m_ParentList->realWidget(),
1138                      i18nc("@title:window", "Downloading"),
1139                      i18n("Download - hit Cancel for abort"));
1140         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
1141         m_Data->m_Svnclient->get(svn::Path(what), to, rev, _peg);
1142     } catch (const svn::Exception &e) {
1143         emit clientException(e.msg());
1144         return false;
1145     }
1146     return true;
1147 }
1148 
1149 /*!
1150     \fn SvnActions::makeDiff(const QString&,const svn::Revision&start,const svn::Revision&end)
1151  */
1152 void SvnActions::makeDiff(const QString &what, const svn::Revision &start, const svn::Revision &end, const svn::Revision &_peg, bool isDir)
1153 {
1154     makeDiff(what, start, what, end, _peg, isDir, m_Data->m_ParentList->realWidget());
1155 }
1156 
1157 void SvnActions::makeDiff(const QString &p1, const svn::Revision &start, const QString &p2, const svn::Revision &end)
1158 {
1159     makeDiff(p1, start, p2, end, nullptr);
1160 }
1161 
1162 void SvnActions::makeDiff(const QString &p1, const svn::Revision &start, const QString &p2, const svn::Revision &end, QWidget *p)
1163 {
1164     if (!doNetworking() && start != svn::Revision::BASE && end != svn::Revision::WORKING) {
1165         emit sendNotify(i18n("Can not do this diff because networking is disabled."));
1166         return;
1167     }
1168     if (m_Data->isExternalDiff()) {
1169         svn::InfoEntry info;
1170         if (singleInfo(p1, start, info)) {
1171             makeDiff(p1, start, p2, end, end, info.isDir(), p);
1172         }
1173         return;
1174     }
1175     makeDiffinternal(p1, start, p2, end, p);
1176 }
1177 
1178 void SvnActions::makeDiffExternal(const QString &p1,
1179                                   const svn::Revision &start,
1180                                   const QString &p2,
1181                                   const svn::Revision &end,
1182                                   const svn::Revision &_peg,
1183                                   bool isDir,
1184                                   QWidget *p,
1185                                   bool rec)
1186 {
1187     QFileInfo f1(p1);
1188     QFileInfo f2(p2);
1189     QTemporaryFile tfile(QDir::tempPath() + QLatin1Char('/') + f1.fileName() + QLatin1Char('-') + start.toString());
1190     QTemporaryFile tfile2(QDir::tempPath() + QLatin1Char('/') + f2.fileName() + QLatin1Char('-') + end.toString());
1191 
1192     QString s1 = f1.fileName() + QLatin1Char('-') + start.toString();
1193     QString s2 = f2.fileName() + QLatin1Char('-') + end.toString();
1194     if (f1.fileName() == f2.fileName() && p1 != p2) {
1195         s2.append(QStringLiteral("-sec"));
1196     }
1197     QTemporaryDir tdir1;
1198     tdir1.setAutoRemove(true);
1199     tfile.setAutoRemove(true);
1200     tfile2.setAutoRemove(true);
1201 
1202     tfile.open();
1203     tfile2.open();
1204 
1205     QString first, second;
1206     svn::Revision peg = _peg;
1207 
1208     if (start != svn::Revision::WORKING) {
1209         first = isDir ? tdir1.path() + QLatin1Char('/') + s1 : tfile.fileName();
1210     } else {
1211         first = p1;
1212     }
1213     if (end != svn::Revision::WORKING) {
1214         second = isDir ? tdir1.path() + QLatin1Char('/') + s2 : tfile2.fileName();
1215     } else {
1216         second = p2;
1217     }
1218     if (second == first) {
1219         KMessageBox::error(m_Data->m_ParentList->realWidget(), i18n("Both entries seems to be the same, can not diff."));
1220         return;
1221     }
1222     if (start != svn::Revision::WORKING) {
1223         if (!isDir) {
1224             if (!get(p1, tfile.fileName(), start, peg, p)) {
1225                 return;
1226             }
1227         } else {
1228             if (!makeCheckout(p1, first, start, peg, rec ? svn::DepthInfinity : svn::DepthFiles, true, false, false, false, false, p)) {
1229                 return;
1230             }
1231         }
1232     }
1233     if (end != svn::Revision::WORKING) {
1234         if (!isDir) {
1235             if (!get(p2, tfile2.fileName(), end, peg, p)) {
1236                 return;
1237             }
1238         } else {
1239             if (!makeCheckout(p2, second, end, peg, rec ? svn::DepthInfinity : svn::DepthFiles, true, false, false, false, false, p)) {
1240                 return;
1241             }
1242         }
1243     }
1244 
1245     const QString edisp = Kdesvnsettings::external_diff_display();
1246     const QVector<QStringRef> wlist = edisp.splitRef(QLatin1Char(' '));
1247     WatchedProcess *proc = new WatchedProcess(this);
1248     for (const QStringRef &str : wlist) {
1249         if (str == QLatin1String("%1")) {
1250             *proc << first;
1251         } else if (str == QLatin1String("%2")) {
1252             *proc << second;
1253         } else {
1254             *proc << str.toString();
1255         }
1256     }
1257     proc->setAutoDelete(true);
1258     proc->setOutputChannelMode(KProcess::MergedChannels);
1259     connect(proc, &WatchedProcess::dataStderrRead, this, &SvnActions::slotProcessDataRead);
1260     connect(proc, &WatchedProcess::dataStdoutRead, this, &SvnActions::slotProcessDataRead);
1261     if (!isDir) {
1262         tfile2.setAutoRemove(false);
1263         tfile.setAutoRemove(false);
1264         proc->appendTempFile(tfile.fileName());
1265         proc->appendTempFile(tfile2.fileName());
1266     } else {
1267         tdir1.setAutoRemove(false);
1268         proc->appendTempDir(tdir1.path());
1269     }
1270     tfile.close();
1271     tfile2.close();
1272 
1273     proc->start();
1274     if (proc->waitForStarted(-1)) {
1275         if (m_Data->runblocked) {
1276             proc->waitForFinished(-1);
1277         }
1278         return;
1279     } else {
1280         emit sendNotify(i18n("Diff-process could not started, check command."));
1281     }
1282 }
1283 
1284 void SvnActions::makeDiff(const QString &p1,
1285                           const svn::Revision &start,
1286                           const QString &p2,
1287                           const svn::Revision &end,
1288                           const svn::Revision &_peg,
1289                           bool isDir,
1290                           QWidget *p)
1291 {
1292     if (m_Data->isExternalDiff()) {
1293         makeDiffExternal(p1, start, p2, end, _peg, isDir, p);
1294     } else {
1295         makeDiffinternal(p1, start, p2, end, p, _peg);
1296     }
1297 }
1298 
1299 void SvnActions::makeDiffinternal(const QString &p1, const svn::Revision &r1, const QString &p2, const svn::Revision &r2, QWidget *p, const svn::Revision &_peg)
1300 {
1301     if (!m_Data->m_CurrentContext) {
1302         return;
1303     }
1304     QByteArray ex;
1305     QTemporaryDir tdir;
1306     tdir.setAutoRemove(true);
1307     QString tn(tdir.path() + QLatin1String("/svndiff"));
1308     QDir d1(tdir.path());
1309     d1.mkdir(QStringLiteral("svndiff"));
1310     bool ignore_content = Kdesvnsettings::diff_ignore_content();
1311     bool gitformat = Kdesvnsettings::diff_gitformat_default();
1312     bool copy_as_add = Kdesvnsettings::diff_copies_as_add();
1313     QWidget *parent = p ? p : m_Data->m_ParentList->realWidget();
1314     QStringList extraOptions;
1315     if (Kdesvnsettings::diff_ignore_spaces()) {
1316         extraOptions.append(QStringLiteral("-b"));
1317     }
1318     if (Kdesvnsettings::diff_ignore_all_white_spaces()) {
1319         extraOptions.append(QStringLiteral("-w"));
1320     }
1321     svn::Revision peg = _peg == svn::Revision::UNDEFINED ? r2 : _peg;
1322     svn::DiffParameter _opts;
1323     _opts.path1(p1)
1324         .path2(p2)
1325         .tmpPath(tn)
1326         .peg(peg)
1327         .rev1(r1)
1328         .rev2(r2)
1329         .ignoreContentType(ignore_content)
1330         .extra(svn::StringArray(extraOptions))
1331         .depth(svn::DepthInfinity)
1332         .ignoreAncestry(false)
1333         .noDiffDeleted(false)
1334         .changeList(svn::StringArray())
1335         .git_diff_format(gitformat)
1336         .copies_as_adds(copy_as_add);
1337 
1338     try {
1339         StopDlg sdlg(m_Data->m_SvnContextListener, parent, i18nc("@title:window", "Diffing"), i18n("Diffing - hit Cancel for abort"));
1340         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
1341         if (p1 == p2 && (r1.isRemote() || r2.isRemote())) {
1342             ex = m_Data->m_Svnclient->diff_peg(_opts);
1343         } else {
1344             ex = m_Data->m_Svnclient->diff(_opts.relativeTo(p1 == p2 ? svn::Path(p1) : svn::Path()));
1345         }
1346     } catch (const svn::Exception &e) {
1347         emit clientException(e.msg());
1348         return;
1349     }
1350     EMIT_FINISHED;
1351     if (ex.isEmpty()) {
1352         emit clientException(i18n("No difference to display"));
1353         return;
1354     }
1355     dispDiff(ex);
1356 }
1357 
1358 void SvnActions::makeNorecDiff(const QString &p1, const svn::Revision &r1, const QString &p2, const svn::Revision &r2, QWidget *_p)
1359 {
1360     if (!m_Data->m_CurrentContext) {
1361         return;
1362     }
1363     if (m_Data->isExternalDiff()) {
1364         svn::InfoEntry info;
1365         if (singleInfo(p1, r1, info)) {
1366             makeDiffExternal(p1, r1, p2, r2, r2, info.isDir(), _p, false);
1367         }
1368         return;
1369     }
1370     QStringList extraOptions;
1371     if (Kdesvnsettings::diff_ignore_spaces()) {
1372         extraOptions.append(QStringLiteral("-b"));
1373     }
1374     if (Kdesvnsettings::diff_ignore_all_white_spaces()) {
1375         extraOptions.append(QStringLiteral("-w"));
1376     }
1377     QByteArray ex;
1378     QTemporaryDir tdir;
1379     tdir.setAutoRemove(true);
1380     QString tn(tdir.path() + QLatin1String("/svndiff"));
1381     QDir d1(tdir.path());
1382     d1.mkdir(QStringLiteral("svndiff"));
1383     bool ignore_content = Kdesvnsettings::diff_ignore_content();
1384     svn::DiffParameter _opts;
1385     // no peg revision required
1386     _opts.path1(p1)
1387         .path2(p2)
1388         .tmpPath(tn)
1389         .rev1(r1)
1390         .rev2(r2)
1391         .ignoreContentType(ignore_content)
1392         .extra(svn::StringArray(extraOptions))
1393         .depth(svn::DepthEmpty)
1394         .ignoreAncestry(false)
1395         .noDiffDeleted(false)
1396         .changeList(svn::StringArray());
1397 
1398     try {
1399         StopDlg sdlg(m_Data->m_SvnContextListener,
1400                      _p ? _p : m_Data->m_ParentList->realWidget(),
1401                      i18nc("@title:window", "Diffing"),
1402                      i18n("Diffing - hit cancel for abort"));
1403         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
1404         ex = m_Data->m_Svnclient->diff(_opts);
1405     } catch (const svn::Exception &e) {
1406         emit clientException(e.msg());
1407         return;
1408     }
1409     EMIT_FINISHED;
1410     if (ex.isEmpty()) {
1411         emit clientException(i18n("No difference to display"));
1412         return;
1413     }
1414 
1415     dispDiff(ex);
1416 }
1417 
1418 void SvnActions::dispDiff(const QByteArray &ex)
1419 {
1420     QString what = Kdesvnsettings::external_diff_display();
1421 
1422     if (Kdesvnsettings::use_external_diff() && (!what.contains(QLatin1String("%1")) || !what.contains(QLatin1String("%2")))) {
1423         const QVector<QStringRef> wlist = what.splitRef(QLatin1Char(' '));
1424         WatchedProcess *proc = new WatchedProcess(this);
1425         bool fname_used = false;
1426 
1427         for (const QStringRef &str : wlist) {
1428             if (str == QLatin1String("%f")) {
1429                 QTemporaryFile tfile;
1430                 tfile.setAutoRemove(false);
1431                 tfile.open();
1432                 fname_used = true;
1433                 QDataStream ds(&tfile);
1434                 ds.writeRawData(ex, ex.size());
1435                 *proc << tfile.fileName();
1436                 proc->appendTempFile(tfile.fileName());
1437                 tfile.close();
1438             } else {
1439                 *proc << str.toString();
1440             }
1441         }
1442         proc->setAutoDelete(true);
1443         proc->setOutputChannelMode(KProcess::MergedChannels);
1444         connect(proc, &WatchedProcess::dataStderrRead, this, &SvnActions::slotProcessDataRead);
1445         connect(proc, &WatchedProcess::dataStdoutRead, this, &SvnActions::slotProcessDataRead);
1446 
1447         proc->start();
1448         if (proc->waitForStarted(-1)) {
1449             if (!fname_used) {
1450                 proc->write(ex);
1451                 proc->closeWriteChannel();
1452             }
1453             if (m_Data->runblocked) {
1454                 proc->waitForFinished(-1);
1455             }
1456             return;
1457         } else {
1458             emit sendNotify(i18n("Display process could not started, check command."));
1459         }
1460     }
1461     bool need_modal = m_Data->runblocked || QApplication::activeModalWidget() != nullptr;
1462     if (need_modal || !m_Data->m_DiffBrowserPtr || !m_Data->m_DiffDialog) {
1463         if (!need_modal && m_Data->m_DiffBrowserPtr) {
1464             delete m_Data->m_DiffBrowserPtr;
1465         }
1466         QPointer<KSvnSimpleOkDialog> dlg(new KSvnSimpleOkDialog(QStringLiteral("diff_display")));
1467         if (!need_modal) {
1468             dlg->setParent(nullptr);
1469         }
1470         dlg->setWindowTitle(i18nc("@title:window", "Diff Display"));
1471         DiffBrowser *ptr(new DiffBrowser(dlg));
1472         ptr->setText(ex);
1473         dlg->addWidget(ptr);
1474         EncodingSelector_impl *enc(new EncodingSelector_impl(dlg));
1475         dlg->addWidget(enc);
1476         connect(enc, &EncodingSelector_impl::TextCodecChanged, ptr, &DiffBrowser::slotTextCodecChanged);
1477         enc->setCurrentEncoding(Kdesvnsettings::locale_for_diff());
1478         // saveAs
1479         QPushButton *pbSaveAs = new QPushButton(dlg->buttonBox());
1480         KStandardGuiItem::assign(pbSaveAs, KStandardGuiItem::SaveAs);
1481         dlg->buttonBox()->addButton(pbSaveAs, QDialogButtonBox::ActionRole);
1482         connect(pbSaveAs, &QAbstractButton::clicked, ptr, &DiffBrowser::saveDiff);
1483 
1484         dlg->buttonBox()->setStandardButtons(QDialogButtonBox::Close);
1485         dlg->addButtonBox();
1486         if (need_modal) {
1487             ptr->setFocus();
1488             dlg->exec();
1489             delete dlg;
1490             return;
1491         } else {
1492             m_Data->m_DiffBrowserPtr = ptr;
1493             m_Data->m_DiffDialog = dlg;
1494         }
1495     } else {
1496         m_Data->m_DiffBrowserPtr->setText(ex);
1497         m_Data->m_DiffBrowserPtr->setFocus();
1498     }
1499     if (m_Data->m_DiffDialog) {
1500         m_Data->m_DiffDialog->show();
1501         m_Data->m_DiffDialog->raise();
1502     }
1503 }
1504 
1505 /*!
1506     \fn SvnActions::makeUpdate(const QString&what,const svn::Revision&rev,bool recurse)
1507  */
1508 void SvnActions::makeUpdate(const svn::Targets &targets, const svn::Revision &rev, svn::Depth depth)
1509 {
1510     if (!m_Data->m_CurrentContext) {
1511         return;
1512     }
1513     svn::Revisions ret;
1514     stopCheckUpdateThread();
1515     try {
1516         StopDlg sdlg(m_Data->m_SvnContextListener,
1517                      m_Data->m_ParentList->realWidget(),
1518                      i18nc("@title:window", "Making update"),
1519                      i18n("Making update - hit Cancel for abort"));
1520         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
1521         svn::UpdateParameter _params;
1522         m_Data->m_SvnContextListener->cleanUpdatedItems();
1523         _params.targets(targets).revision(rev).depth(depth).ignore_externals(false).allow_unversioned(false).sticky_depth(true);
1524         ret = m_Data->m_Svnclient->update(_params);
1525     } catch (const svn::Exception &e) {
1526         emit clientException(e.msg());
1527         return;
1528     }
1529     removeFromUpdateCache(m_Data->m_SvnContextListener->updatedItems(), true);
1530     // removeFromUpdateCache(what,depth==svn::DepthFiles);
1531     EMIT_REFRESH;
1532     EMIT_FINISHED;
1533     m_Data->clearCaches();
1534 }
1535 
1536 /*!
1537     \fn SvnActions::slotUpdateHeadRec()
1538  */
1539 void SvnActions::slotUpdateHeadRec()
1540 {
1541     prepareUpdate(false);
1542 }
1543 
1544 /*!
1545     \fn SvnActions::prepareUpdate(bool ask)
1546  */
1547 void SvnActions::prepareUpdate(bool ask)
1548 {
1549     if (!m_Data->m_ParentList || !m_Data->m_ParentList->isWorkingCopy()) {
1550         return;
1551     }
1552     const SvnItemList k = m_Data->m_ParentList->SelectionList();
1553 
1554     svn::Paths what;
1555     if (k.isEmpty()) {
1556         what.append(svn::Path(m_Data->m_ParentList->baseUri()));
1557     } else {
1558         what.reserve(k.size());
1559         for (const SvnItem *item : k)
1560             what.append(svn::Path(item->fullName()));
1561     }
1562     svn::Revision r(svn::Revision::HEAD);
1563     if (ask) {
1564         Rangeinput_impl::revision_range range;
1565         if (!Rangeinput_impl::getRevisionRange(range, true, true)) {
1566             return;
1567         }
1568         r = range.first;
1569     }
1570     makeUpdate(svn::Targets(what), r, svn::DepthUnknown);
1571 }
1572 
1573 /*!
1574     \fn SvnActions::slotUpdateTo()
1575  */
1576 void SvnActions::slotUpdateTo()
1577 {
1578     prepareUpdate(true);
1579 }
1580 
1581 /*!
1582     \fn SvnActions::slotAdd()
1583  */
1584 void SvnActions::slotAdd()
1585 {
1586     makeAdd(false);
1587 }
1588 
1589 void SvnActions::slotAddRec()
1590 {
1591     makeAdd(true);
1592 }
1593 
1594 void SvnActions::makeAdd(bool rec)
1595 {
1596     if (!m_Data->m_CurrentContext) {
1597         return;
1598     }
1599     if (!m_Data->m_ParentList) {
1600         return;
1601     }
1602     const SvnItemList lst = m_Data->m_ParentList->SelectionList();
1603     if (lst.isEmpty()) {
1604         KMessageBox::error(m_Data->m_ParentList->realWidget(), i18n("Which files or directories should I add?"));
1605         return;
1606     }
1607     svn::Paths items;
1608     items.reserve(lst.size());
1609     for (const SvnItem *cur : lst) {
1610         if (cur->isVersioned()) {
1611             KMessageBox::error(m_Data->m_ParentList->realWidget(), i18n("<center>The entry<br/>%1<br/>is versioned - break.</center>", cur->fullName()));
1612             return;
1613         }
1614         items.push_back(svn::Path(cur->fullName()));
1615     }
1616     addItems(items, (rec ? svn::DepthInfinity : svn::DepthEmpty));
1617     emit sigRefreshCurrent(nullptr);
1618 }
1619 
1620 bool SvnActions::addItems(const svn::Paths &items, svn::Depth depth)
1621 {
1622     try {
1623         for (const svn::Path &item : items) {
1624             m_Data->m_Svnclient->add(item, depth);
1625         }
1626     } catch (const svn::Exception &e) {
1627         emit clientException(e.msg());
1628         return false;
1629     }
1630     return true;
1631 }
1632 
1633 bool SvnActions::makeDelete(const QStringList &w)
1634 {
1635     KMessageBox::ButtonCode answer = KMessageBox::warningTwoActionsList(nullptr,
1636                                                                         i18n("Really delete these entries?"),
1637                                                                         w,
1638                                                                         i18n("Delete from repository"),
1639                                                                         KStandardGuiItem::del(),
1640                                                                         KStandardGuiItem::cancel());
1641     if (answer != KMessageBox::PrimaryAction) {
1642         return false;
1643     }
1644     return makeDelete(svn::Targets::fromStringList(w));
1645 }
1646 
1647 /*!
1648     \fn SvnActions::makeDelete()
1649  */
1650 bool SvnActions::makeDelete(const svn::Targets &target, bool keep_local, bool force)
1651 {
1652     if (!m_Data->m_CurrentContext) {
1653         return false;
1654     }
1655     try {
1656         m_Data->m_Svnclient->remove(target, force, keep_local);
1657     } catch (const svn::Exception &e) {
1658         emit clientException(e.msg());
1659         return false;
1660     }
1661     EMIT_FINISHED;
1662     return true;
1663 }
1664 
1665 void SvnActions::slotCheckout()
1666 {
1667     CheckoutExport(QUrl(), false);
1668 }
1669 
1670 void SvnActions::slotExport()
1671 {
1672     CheckoutExport(QUrl(), true);
1673 }
1674 
1675 void SvnActions::slotCheckoutCurrent()
1676 {
1677     CheckoutExportCurrent(false);
1678 }
1679 
1680 void SvnActions::slotExportCurrent()
1681 {
1682     CheckoutExportCurrent(true);
1683 }
1684 
1685 void SvnActions::CheckoutExport(const QUrl &what, bool _exp, bool urlisTarget)
1686 {
1687     QPointer<KSvnSimpleOkDialog> dlg(new KSvnSimpleOkDialog(QStringLiteral("checkout_export_dialog")));
1688     CheckoutInfo_impl *ptr(new CheckoutInfo_impl(dlg));
1689     dlg->setWindowTitle(_exp ? i18nc("@title:window", "Export a Repository") : i18nc("@title:window", "Checkout a Repository"));
1690     dlg->setWithCancelButton();
1691 
1692     if (!what.isEmpty()) {
1693         if (!urlisTarget) {
1694             ptr->setStartUrl(what);
1695         } else {
1696             ptr->setTargetUrl(what);
1697         }
1698     }
1699     ptr->hideIgnoreKeywords(!_exp);
1700     ptr->hideOverwrite(!_exp);
1701     dlg->addWidget(ptr);
1702     if (dlg->exec() == QDialog::Accepted) {
1703         svn::Revision r = ptr->toRevision();
1704         bool openit = ptr->openAfterJob();
1705         bool ignoreExternal = ptr->ignoreExternals();
1706         if (!ptr->reposURL().isValid()) {
1707             KMessageBox::error(QApplication::activeModalWidget(), i18n("Invalid url given!"), _exp ? i18n("Export repository") : i18n("Checkout a repository"));
1708             delete dlg;
1709             return;
1710         }
1711         if (ptr->targetDir().isEmpty()) {
1712             KMessageBox::error(QApplication::activeModalWidget(),
1713                                i18n("Invalid local path given!"),
1714                                _exp ? i18n("Export repository") : i18n("Checkout a repository"));
1715             delete dlg;
1716             return;
1717         }
1718         // svn::Path should not take a QString but a QByteArray ...
1719         const QString rUrl(QString::fromUtf8(ptr->reposURL().toEncoded()));
1720         makeCheckout(rUrl, ptr->targetDir(), r, r, ptr->getDepth(), _exp, openit, ignoreExternal, ptr->overwrite(), ptr->ignoreKeywords(), nullptr);
1721     }
1722     delete dlg;
1723 }
1724 
1725 void SvnActions::CheckoutExportCurrent(bool _exp)
1726 {
1727     // checkout export only on repo, not wc
1728     if (!m_Data->m_ParentList || m_Data->m_ParentList->isWorkingCopy()) {
1729         return;
1730     }
1731     SvnItem *k = m_Data->m_ParentList->Selected();
1732     if (k && !k->isDir()) {
1733         KMessageBox::error(m_Data->m_ParentList->realWidget(), _exp ? i18n("Exporting a file?") : i18n("Checking out a file?"));
1734         return;
1735     }
1736     QUrl what;
1737     if (!k) {
1738         what = QUrl(m_Data->m_ParentList->baseUri());
1739     } else {
1740         what = QUrl(k->fullName());
1741     }
1742     // what is always remote, so QUrl(what) is fine
1743     CheckoutExport(QUrl(what), _exp);
1744 }
1745 
1746 bool SvnActions::makeCheckout(const QString &rUrl,
1747                               const QString &tPath,
1748                               const svn::Revision &r,
1749                               const svn::Revision &_peg,
1750                               svn::Depth depth,
1751                               // kind of operation
1752                               bool _exp,
1753                               // open after job
1754                               bool openIt,
1755                               // ignore externals
1756                               bool ignoreExternal,
1757                               // overwrite/force not versioned items
1758                               bool overwrite,
1759                               // do not replace svn:keywords on export
1760                               bool ignoreKeywords,
1761                               QWidget *_p)
1762 {
1763     QString fUrl = rUrl;
1764     while (fUrl.endsWith(QLatin1Char('/'))) {
1765         fUrl.chop(1);
1766     }
1767     // can only be a local target dir
1768     svn::Path p(tPath);
1769     svn::Revision peg = _peg;
1770     if (r != svn::Revision::BASE && r != svn::Revision::WORKING && _peg == svn::Revision::UNDEFINED) {
1771         peg = r;
1772     }
1773     if (!_exp || !m_Data->m_CurrentContext) {
1774         reInitClient();
1775     }
1776     svn::CheckoutParameter cparams;
1777     cparams.moduleName(fUrl)
1778         .destination(p)
1779         .revision(r)
1780         .peg(peg)
1781         .depth(depth)
1782         .ignoreExternals(ignoreExternal)
1783         .overWrite(overwrite)
1784         .ignoreKeywords(ignoreKeywords);
1785 
1786     try {
1787         StopDlg sdlg(m_Data->m_SvnContextListener,
1788                      _p ? _p : m_Data->m_ParentList->realWidget(),
1789                      _exp ? i18nc("@title:window", "Export") : i18nc("@title:window", "Checkout"),
1790                      _exp ? i18n("Exporting") : i18n("Checking out"));
1791         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
1792         if (_exp) {
1793             /// @todo setup parameter for export operation
1794             m_Data->m_Svnclient->doExport(cparams.nativeEol(QString()));
1795         } else {
1796             m_Data->m_Svnclient->checkout(cparams);
1797         }
1798     } catch (const svn::Exception &e) {
1799         emit clientException(e.msg());
1800         return false;
1801     }
1802     if (openIt) {
1803         const QUrl url(QUrl::fromLocalFile(tPath));
1804         if (!_exp) {
1805             emit sigGotourl(url);
1806         } else {
1807             QDesktopServices::openUrl(url);
1808         }
1809     }
1810     EMIT_FINISHED;
1811 
1812     return true;
1813 }
1814 
1815 void SvnActions::slotRevert()
1816 {
1817     if (!m_Data->m_ParentList || !m_Data->m_ParentList->isWorkingCopy()) {
1818         return;
1819     }
1820     const SvnItemList lst = m_Data->m_ParentList->SelectionList();
1821     QStringList displist;
1822     if (!lst.isEmpty()) {
1823         svn::StatusParameter params;
1824         params.depth(svn::DepthInfinity).all(false).update(false).noIgnore(false).revision(svn::Revision::HEAD);
1825         for (const SvnItem *cur : lst) {
1826             if (!cur->isVersioned()) {
1827                 KMessageBox::error(m_Data->m_ParentList->realWidget(),
1828                                    i18n("<center>The entry<br/>%1<br/>is not versioned - break.</center>", cur->fullName()));
1829                 return;
1830             }
1831             displist.append(cur->fullName());
1832         }
1833     } else {
1834         displist.push_back(m_Data->m_ParentList->baseUri());
1835     }
1836 
1837     slotRevertItems(displist);
1838     EMIT_REFRESH;
1839 }
1840 
1841 void SvnActions::slotRevertItems(const QStringList &displist)
1842 {
1843     if (!m_Data->m_CurrentContext) {
1844         return;
1845     }
1846     if (displist.isEmpty()) {
1847         return;
1848     }
1849 
1850     QPointer<RevertForm> dlg(new RevertForm(displist, QApplication::activeModalWidget()));
1851     if (dlg->exec() != QDialog::Accepted) {
1852         delete dlg;
1853         return;
1854     }
1855     const svn::Depth depth = dlg->getDepth();
1856     delete dlg;
1857 
1858     const svn::Targets target(svn::Targets::fromStringList(displist));
1859     try {
1860         StopDlg sdlg(m_Data->m_SvnContextListener, m_Data->m_ParentList->realWidget(), i18nc("@title:window", "Revert"), i18n("Reverting items"));
1861         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
1862         m_Data->m_Svnclient->revert(target, depth);
1863     } catch (const svn::Exception &e) {
1864         emit clientException(e.msg());
1865         return;
1866     }
1867     // remove them from cache
1868     for (const auto &tgt : target.targets()) {
1869         m_Data->m_Cache.deleteKey(tgt.path(), depth != svn::DepthInfinity);
1870     }
1871     emit sigItemsReverted(displist);
1872     EMIT_FINISHED;
1873 }
1874 
1875 bool SvnActions::makeSwitch(const QUrl &rUrl,
1876                             const QString &tPath,
1877                             const svn::Revision &r,
1878                             svn::Depth depth,
1879                             const svn::Revision &peg,
1880                             bool stickydepth,
1881                             bool ignore_externals,
1882                             bool allow_unversioned)
1883 {
1884     if (!m_Data->m_CurrentContext) {
1885         return false;
1886     }
1887     svn::Path p(tPath);
1888     try {
1889         StopDlg sdlg(m_Data->m_SvnContextListener, m_Data->m_ParentList->realWidget(), i18nc("@title:window", "Switch URL"), i18n("Switching URL"));
1890         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
1891         m_Data->m_Svnclient->doSwitch(p, svn::Url(rUrl), r, depth, peg, stickydepth, ignore_externals, allow_unversioned);
1892     } catch (const svn::Exception &e) {
1893         emit clientException(e.msg());
1894         return false;
1895     }
1896     m_Data->clearCaches();
1897     EMIT_FINISHED;
1898     return true;
1899 }
1900 
1901 bool SvnActions::makeRelocate(const QUrl &fUrl, const QUrl &tUrl, const QString &path, bool recursive, bool ignore_externals)
1902 {
1903     if (!m_Data->m_CurrentContext) {
1904         return false;
1905     }
1906     svn::Path p(path);
1907     try {
1908         StopDlg sdlg(m_Data->m_SvnContextListener,
1909                      m_Data->m_ParentList->realWidget(),
1910                      i18nc("@title:window", "Relocate Repository"),
1911                      i18n("Relocate repository to new URL"));
1912         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
1913         m_Data->m_Svnclient->relocate(p, svn::Url(fUrl), svn::Url(tUrl), recursive, ignore_externals);
1914     } catch (const svn::Exception &e) {
1915         emit clientException(e.msg());
1916         return false;
1917     }
1918     m_Data->clearCaches();
1919     EMIT_FINISHED;
1920     return true;
1921 }
1922 
1923 void SvnActions::slotSwitch()
1924 {
1925     if (!m_Data->m_CurrentContext) {
1926         return;
1927     }
1928     if (!m_Data->m_ParentList || !m_Data->m_ParentList->isWorkingCopy()) {
1929         return;
1930     }
1931 
1932     const SvnItemList lst = m_Data->m_ParentList->SelectionList();
1933     if (lst.count() > 1) {
1934         KMessageBox::error(nullptr, i18n("Can only switch one item at time"));
1935         return;
1936     }
1937 
1938     SvnItem *k = m_Data->m_ParentList->SelectedOrMain();
1939     if (!k) {
1940         KMessageBox::error(nullptr, i18n("Error getting entry to switch"));
1941         return;
1942     }
1943     const QUrl what = k->Url();
1944     if (makeSwitch(k->fullName(), what)) {
1945         emit reinitItem(k);
1946     }
1947 }
1948 
1949 bool SvnActions::makeSwitch(const QString &path, const QUrl &what)
1950 {
1951     QPointer<KSvnSimpleOkDialog> dlg(new KSvnSimpleOkDialog(QStringLiteral("switch_url_dlg")));
1952     CheckoutInfo_impl *ptr(new CheckoutInfo_impl(dlg));
1953     dlg->setWindowTitle(i18nc("@title:window", "Switch URL"));
1954     dlg->setWithCancelButton();
1955     ptr->setStartUrl(what);
1956     ptr->disableAppend(true);
1957     ptr->disableTargetDir(true);
1958     ptr->disableOpen(true);
1959     dlg->addWidget(ptr);
1960     bool done = false;
1961     if (dlg->exec() == QDialog::Accepted) {
1962         if (!ptr->reposURL().isValid()) {
1963             KMessageBox::error(QApplication::activeModalWidget(), i18n("Invalid url given!"), i18n("Switch URL"));
1964             delete dlg;
1965             return false;
1966         }
1967         svn::Revision r = ptr->toRevision();
1968         done = makeSwitch(ptr->reposURL(), path, r, ptr->getDepth(), r, true, ptr->ignoreExternals(), ptr->overwrite());
1969     }
1970     delete dlg;
1971     return done;
1972 }
1973 
1974 bool SvnActions::makeCleanup(const QString &path)
1975 {
1976     if (!m_Data->m_CurrentContext) {
1977         return false;
1978     }
1979     try {
1980         StopDlg sdlg(m_Data->m_SvnContextListener, m_Data->m_ParentList->realWidget(), i18nc("@title:window", "Cleanup"), i18n("Cleaning up folder"));
1981         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
1982         m_Data->m_Svnclient->cleanup(svn::Path(path));
1983     } catch (const svn::Exception &e) {
1984         emit clientException(e.msg());
1985         return false;
1986     }
1987     return true;
1988 }
1989 
1990 void SvnActions::slotResolved(const QString &path)
1991 {
1992     if (!m_Data->m_CurrentContext) {
1993         return;
1994     }
1995     try {
1996         StopDlg sdlg(m_Data->m_SvnContextListener, m_Data->m_ParentList->realWidget(), i18nc("@title:window", "Resolve"), i18n("Marking resolved"));
1997         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
1998         m_Data->m_Svnclient->resolve(svn::Path(path), svn::DepthEmpty);
1999     } catch (const svn::Exception &e) {
2000         emit clientException(e.msg());
2001         return;
2002     }
2003     m_Data->m_conflictCache.deleteKey(path, false);
2004     emit sigRefreshItem(path);
2005 }
2006 
2007 void SvnActions::slotResolve(const QString &p)
2008 {
2009     if (!m_Data->m_CurrentContext) {
2010         return;
2011     }
2012     const QString eresolv = Kdesvnsettings::conflict_resolver();
2013     const QVector<QStringRef> wlist = eresolv.splitRef(QLatin1Char(' '));
2014     if (wlist.isEmpty()) {
2015         return;
2016     }
2017     svn::InfoEntry i1;
2018     if (!singleInfo(p, svn::Revision::UNDEFINED, i1)) {
2019         return;
2020     }
2021     QFileInfo fi(p);
2022     QString base;
2023     if (fi.isRelative()) {
2024         base = fi.absolutePath() + QLatin1Char('/');
2025     }
2026     if (i1.conflicts().isEmpty()) {
2027         emit sendNotify(i18n("Could not retrieve conflict information - giving up."));
2028         return;
2029     }
2030 
2031     WatchedProcess *proc = new WatchedProcess(this);
2032     for (const QStringRef &str : wlist) {
2033         if (str == QLatin1String("%o") || str == QLatin1String("%l")) {
2034             *proc << i1.conflicts()[0]->baseFile();
2035         } else if (str == QLatin1String("%m") || str == QLatin1String("%w")) {
2036             *proc << i1.conflicts()[0]->myFile();
2037         } else if (str == QLatin1String("%n") || str == QLatin1String("%r")) {
2038             *proc << i1.conflicts()[0]->theirFile();
2039         } else if (str == QLatin1String("%t")) {
2040             *proc << p;
2041         } else {
2042             *proc << str.toString();
2043         }
2044     }
2045     proc->setAutoDelete(true);
2046     proc->setOutputChannelMode(KProcess::MergedChannels);
2047     connect(proc, &WatchedProcess::dataStderrRead, this, &SvnActions::slotProcessDataRead);
2048     connect(proc, &WatchedProcess::dataStdoutRead, this, &SvnActions::slotProcessDataRead);
2049 
2050     proc->start();
2051     if (!proc->waitForStarted(-1)) {
2052         emit sendNotify(i18n("Resolve-process could not started, check command."));
2053     }
2054 }
2055 
2056 void SvnActions::slotImport(const QString &path, const QUrl &target, const QString &message, svn::Depth depth, bool noIgnore, bool noUnknown)
2057 {
2058     if (!m_Data->m_CurrentContext) {
2059         return;
2060     }
2061     try {
2062         StopDlg sdlg(m_Data->m_SvnContextListener, m_Data->m_ParentList->realWidget(), i18nc("@title:window", "Import"), i18n("Importing items"));
2063         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
2064         m_Data->m_Svnclient->import(svn::Path(path), svn::Url(target), message, depth, noIgnore, noUnknown);
2065     } catch (const svn::Exception &e) {
2066         emit clientException(e.msg());
2067         return;
2068     }
2069 }
2070 
2071 void SvnActions::slotMergeExternal(const QString &_src1,
2072                                    const QString &_src2,
2073                                    const QString &_target,
2074                                    const svn::Revision &rev1,
2075                                    const svn::Revision &rev2,
2076                                    const svn::Revision &_peg,
2077                                    bool rec)
2078 {
2079     Q_UNUSED(_peg);
2080     QTemporaryDir tdir1;
2081     tdir1.setAutoRemove(true);
2082     QString src1 = _src1;
2083     QString src2 = _src2;
2084     QString target = _target;
2085     bool singleMerge = false;
2086 
2087     if (rev1 == rev2 && (src2.isEmpty() || src1 == src2)) {
2088         singleMerge = true;
2089     }
2090     if (src1.isEmpty()) {
2091         emit clientException(i18n("Nothing to merge."));
2092         return;
2093     }
2094     if (target.isEmpty()) {
2095         emit clientException(i18n("No destination to merge."));
2096         return;
2097     }
2098 
2099     QFileInfo f1(src1);
2100     QFileInfo f2(src2);
2101     bool isDir = true;
2102 
2103     svn::InfoEntry i1, i2;
2104 
2105     if (!singleInfo(src1, rev1, i1)) {
2106         return;
2107     }
2108     isDir = i1.isDir();
2109     if (!singleMerge && src1 != src2) {
2110         if (!singleInfo(src2, rev2, i2)) {
2111             return;
2112         }
2113         if (i2.isDir() != isDir) {
2114             emit clientException(i18n("Both sources must be same type."));
2115             return;
2116         }
2117     }
2118 
2119     QFileInfo ti(target);
2120     if (ti.isDir() != isDir) {
2121         emit clientException(i18n("Target for merge must same type like sources."));
2122         return;
2123     }
2124 
2125     QString s1 = f1.fileName() + QLatin1Char('-') + rev1.toString();
2126     QString s2 = f2.fileName() + QLatin1Char('-') + rev2.toString();
2127     QString first, second;
2128     if (rev1 != svn::Revision::WORKING) {
2129         first = tdir1.path() + QLatin1Char('/') + s1;
2130     } else {
2131         first = src1;
2132     }
2133     if (!singleMerge) {
2134         if (rev2 != svn::Revision::WORKING) {
2135             second = tdir1.path() + QLatin1Char('/') + s2;
2136         } else {
2137             second = src2;
2138         }
2139     } else {
2140         // only two-way  merge
2141         second.clear();
2142     }
2143     if (second == first) {
2144         KMessageBox::error(m_Data->m_ParentList->realWidget(), i18n("Both entries seems to be the same, will not do a merge."));
2145         return;
2146     }
2147 
2148     if (rev1 != svn::Revision::WORKING) {
2149         if (isDir) {
2150             if (!makeCheckout(src1,
2151                               first,
2152                               rev1,
2153                               svn::Revision::UNDEFINED,
2154                               rec ? svn::DepthInfinity : svn::DepthFiles,
2155                               true,
2156                               false,
2157                               false,
2158                               false,
2159                               false,
2160                               nullptr)) {
2161                 return;
2162             }
2163         } else {
2164             if (!get(src1, first, rev1, svn::Revision::UNDEFINED, m_Data->m_ParentList->realWidget())) {
2165                 return;
2166             }
2167         }
2168     }
2169 
2170     if (!singleMerge) {
2171         if (rev2 != svn::Revision::WORKING) {
2172             if (isDir) {
2173                 if (!makeCheckout(src2,
2174                                   second,
2175                                   rev2,
2176                                   svn::Revision::UNDEFINED,
2177                                   rec ? svn::DepthInfinity : svn::DepthFiles,
2178                                   true,
2179                                   false,
2180                                   false,
2181                                   false,
2182                                   false,
2183                                   nullptr)) {
2184                     return;
2185                 }
2186             } else {
2187                 if (!get(src2, second, rev2, svn::Revision::UNDEFINED, m_Data->m_ParentList->realWidget())) {
2188                     return;
2189                 }
2190             }
2191         }
2192     }
2193     const QString edisp = Kdesvnsettings::external_merge_program();
2194     const QVector<QStringRef> wlist = edisp.splitRef(QLatin1Char(' '));
2195     WatchedProcess *proc = new WatchedProcess(this);
2196     for (const QStringRef &str : wlist) {
2197         if (str == QLatin1String("%s1")) {
2198             *proc << first;
2199         } else if (str == QLatin1String("%s2")) {
2200             if (!second.isEmpty()) {
2201                 *proc << second;
2202             }
2203         } else if (str == QLatin1String("%t")) {
2204             *proc << target;
2205         } else {
2206             *proc << str.toString();
2207         }
2208     }
2209     tdir1.setAutoRemove(false);
2210     proc->setAutoDelete(true);
2211     proc->appendTempDir(tdir1.path());
2212     proc->setOutputChannelMode(KProcess::MergedChannels);
2213     connect(proc, &WatchedProcess::dataStderrRead, this, &SvnActions::slotProcessDataRead);
2214     connect(proc, &WatchedProcess::dataStdoutRead, this, &SvnActions::slotProcessDataRead);
2215 
2216     proc->start();
2217     if (proc->waitForStarted(-1)) {
2218         if (m_Data->runblocked) {
2219             proc->waitForFinished(-1);
2220         }
2221     } else {
2222         emit sendNotify(i18n("Merge process could not started, check command."));
2223     }
2224 }
2225 
2226 void SvnActions::slotMergeWcRevisions(const QString &_entry,
2227                                       const svn::Revision &rev1,
2228                                       const svn::Revision &rev2,
2229                                       bool rec,
2230                                       bool ancestry,
2231                                       bool forceIt,
2232                                       bool dry,
2233                                       bool allow_mixed_rev)
2234 {
2235     slotMerge(_entry, _entry, _entry, rev1, rev2, svn::Revision::UNDEFINED, rec, ancestry, forceIt, dry, false, false, allow_mixed_rev);
2236 }
2237 
2238 void SvnActions::slotMerge(const QString &src1,
2239                            const QString &src2,
2240                            const QString &target,
2241                            const svn::Revision &rev1,
2242                            const svn::Revision &rev2,
2243                            const svn::Revision &_peg,
2244                            bool rec,
2245                            bool ancestry,
2246                            bool forceIt,
2247                            bool dry,
2248                            bool recordOnly,
2249                            bool reintegrate,
2250                            bool allow_mixed_rev)
2251 {
2252     Q_UNUSED(_peg);
2253     if (!m_Data->m_CurrentContext) {
2254         return;
2255     }
2256 
2257     svn::Revision peg = svn::Revision::HEAD;
2258     svn::Revision tpeg;
2259     svn::RevisionRanges ranges;
2260     svn::Path p1;
2261     try {
2262         svn::Path::parsePeg(src1, p1, tpeg);
2263     } catch (const svn::Exception &e) {
2264         emit clientException(e.msg());
2265         return;
2266     }
2267     if (tpeg != svn::Revision::UNDEFINED) {
2268         peg = tpeg;
2269     }
2270     svn::Path p2(src2);
2271 
2272     bool pegged_merge = false;
2273 
2274     // build merge Parameters
2275     svn::MergeParameter _merge_parameter;
2276     ranges.append(svn::RevisionRange(rev1, rev2));
2277     _merge_parameter.revisions(ranges)
2278         .path1(p1)
2279         .path2(p2)
2280         .depth(rec ? svn::DepthInfinity : svn::DepthFiles)
2281         .notice_ancestry(ancestry)
2282         .force(forceIt)
2283         .dry_run(dry)
2284         .record_only(recordOnly)
2285         .reintegrate(reintegrate)
2286         .allow_mixed_rev(allow_mixed_rev)
2287         .localPath(svn::Path(target))
2288         .merge_options(svn::StringArray());
2289 
2290     if (!reintegrate && (!p2.isSet() || src1 == src2)) {
2291         // pegged merge
2292         pegged_merge = true;
2293         if (peg == svn::Revision::UNDEFINED) {
2294             if (p1.isUrl()) {
2295                 peg = rev2;
2296             } else {
2297                 peg = svn::Revision::WORKING;
2298             }
2299         }
2300         _merge_parameter.peg(peg);
2301     }
2302 
2303     try {
2304         StopDlg sdlg(m_Data->m_SvnContextListener, m_Data->m_ParentList->realWidget(), i18nc("@title:window", "Merge"), i18n("Merging items"));
2305         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
2306         if (pegged_merge) {
2307             m_Data->m_Svnclient->merge_peg(_merge_parameter);
2308         } else {
2309             m_Data->m_Svnclient->merge(_merge_parameter);
2310         }
2311     } catch (const svn::Exception &e) {
2312         emit clientException(e.msg());
2313         return;
2314     }
2315     m_Data->clearCaches();
2316 }
2317 
2318 /*!
2319     \fn SvnActions::slotCopyMove(bool,const QString&,const QString&)
2320  */
2321 bool SvnActions::makeMove(const QString &Old, const QString &New)
2322 {
2323     if (!m_Data->m_CurrentContext) {
2324         return false;
2325     }
2326     svn::CopyParameter params(Old, New);
2327     svn::Revision nnum;
2328 
2329     try {
2330         StopDlg sdlg(m_Data->m_SvnContextListener, m_Data->m_ParentList->realWidget(), i18nc("@title:window", "Move"), i18n("Moving/Rename item"));
2331         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
2332         nnum = m_Data->m_Svnclient->move(params.asChild(false).makeParent(false));
2333     } catch (const svn::Exception &e) {
2334         emit clientException(e.msg());
2335         return false;
2336     }
2337     if (nnum != svn::Revision::UNDEFINED) {
2338         emit sendNotify(i18n("Committed revision %1.", nnum.toString()));
2339     }
2340     EMIT_REFRESH;
2341     return true;
2342 }
2343 
2344 bool SvnActions::makeMove(const QList<QUrl> &Old, const QString &New)
2345 {
2346     try {
2347         StopDlg sdlg(m_Data->m_SvnContextListener, m_Data->m_ParentList->realWidget(), i18nc("@title:window", "Move"), i18n("Moving entries"));
2348         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
2349         const svn::Path pNew(New);
2350         // either both are local paths -> move in wc, or both are urls -> move in repository
2351         const svn::Targets t(
2352             svn::Targets::fromUrlList(Old, pNew.isUrl() ? svn::Targets::UrlConversion::KeepUrl : svn::Targets::UrlConversion::PreferLocalPath));
2353         m_Data->m_Svnclient->move(svn::CopyParameter(t, pNew).asChild(true).makeParent(false));
2354     } catch (const svn::Exception &e) {
2355         emit clientException(e.msg());
2356         return false;
2357     }
2358     return true;
2359 }
2360 
2361 bool SvnActions::makeCopy(const QString &Old, const QString &New, const svn::Revision &rev)
2362 {
2363     if (!m_Data->m_CurrentContext) {
2364         return false;
2365     }
2366     try {
2367         StopDlg sdlg(m_Data->m_SvnContextListener, m_Data->m_ParentList->realWidget(), i18nc("@title:window", "Copy / Move"), i18n("Copy or Moving entries"));
2368         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
2369         m_Data->m_Svnclient->copy(svn::Path(Old), rev, svn::Path(New));
2370     } catch (const svn::Exception &e) {
2371         emit clientException(e.msg());
2372         return false;
2373     }
2374     EMIT_REFRESH;
2375     return true;
2376 }
2377 
2378 bool SvnActions::makeCopy(const QList<QUrl> &Old, const QString &New, const svn::Revision &rev)
2379 {
2380     try {
2381         StopDlg sdlg(m_Data->m_SvnContextListener, m_Data->m_ParentList->realWidget(), i18nc("@title:window", "Copy / Move"), i18n("Copy or Moving entries"));
2382         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
2383         const svn::Path pNew(New);
2384         // either both are local paths -> copy in wc, or both are urls -> copy in repository
2385         const svn::Targets t(
2386             svn::Targets::fromUrlList(Old, pNew.isUrl() ? svn::Targets::UrlConversion::KeepUrl : svn::Targets::UrlConversion::PreferLocalPath));
2387         m_Data->m_Svnclient->copy(svn::CopyParameter(t, pNew).srcRevision(rev).pegRevision(rev).asChild(true));
2388     } catch (const svn::Exception &e) {
2389         emit clientException(e.msg());
2390         return false;
2391     }
2392     return true;
2393 }
2394 
2395 /*!
2396     \fn SvnActions::makeLock(const QStringList&)
2397  */
2398 void SvnActions::makeLock(const QStringList &what, const QString &_msg, bool breakit)
2399 {
2400     if (!m_Data->m_CurrentContext) {
2401         return;
2402     }
2403     try {
2404         m_Data->m_Svnclient->lock(svn::Targets::fromStringList(what), _msg, breakit);
2405     } catch (const svn::Exception &e) {
2406         emit clientException(e.msg());
2407         return;
2408     }
2409 }
2410 
2411 /*!
2412     \fn SvnActions::makeUnlock(const QStringList&)
2413  */
2414 void SvnActions::makeUnlock(const QStringList &what, bool breakit)
2415 {
2416     if (!m_Data->m_CurrentContext) {
2417         return;
2418     }
2419     try {
2420         m_Data->m_Svnclient->unlock(svn::Targets::fromStringList(what), breakit);
2421     } catch (const svn::Exception &e) {
2422         emit clientException(e.msg());
2423         return;
2424     }
2425     for (const QString &key : what) {
2426         m_Data->m_repoLockCache.deleteKey(key, true);
2427     }
2428     //    m_Data->m_repoLockCache.dump_tree();
2429 }
2430 
2431 /*!
2432     \fn SvnActions::makeStatus(const QString&what, svn::StatusEntries&dlist)
2433  */
2434 bool SvnActions::makeStatus(const QString &what, svn::StatusEntries &dlist, const svn::Revision &where, bool rec, bool all)
2435 {
2436     bool display_ignores = Kdesvnsettings::display_ignored_files();
2437     return makeStatus(what, dlist, where, rec, all, display_ignores);
2438 }
2439 
2440 bool SvnActions::makeStatus(const QString &what, svn::StatusEntries &dlist, const svn::Revision &where, bool rec, bool all, bool display_ignores, bool updates)
2441 {
2442     svn::Depth _d = rec ? svn::DepthInfinity : svn::DepthImmediates;
2443     return makeStatus(what, dlist, where, _d, all, display_ignores, updates);
2444 }
2445 
2446 bool SvnActions::makeStatus(const QString &what,
2447                             svn::StatusEntries &dlist,
2448                             const svn::Revision &where,
2449                             svn::Depth _d,
2450                             bool all,
2451                             bool display_ignores,
2452                             bool updates)
2453 {
2454     bool disp_remote_details = Kdesvnsettings::details_on_remote_listing();
2455     try {
2456 #ifdef DEBUG_TIMER
2457         QTime _counttime;
2458         _counttime.start();
2459 #endif
2460         svn::StatusParameter params(what);
2461         StopDlg sdlg(m_Data->m_SvnContextListener,
2462                      m_Data->m_ParentList->realWidget(),
2463                      i18nc("@title:window", "Status / List"),
2464                      i18n("Creating list / check status"));
2465         connect(this, &SvnActions::sigExtraLogMsg, &sdlg, &StopDlg::slotExtraMessage);
2466         //                                      rec all  up     noign
2467         dlist = m_Data->m_Svnclient->status(
2468             params.depth(_d).all(all).update(updates).noIgnore(display_ignores).revision(where).detailedRemote(disp_remote_details).ignoreExternals(false));
2469 #ifdef DEBUG_TIMER
2470         qCDebug(KDESVN_LOG) << "Time for getting status: " << _counttime.elapsed();
2471 #endif
2472 
2473     } catch (const svn::Exception &e) {
2474         emit clientException(e.msg());
2475         return false;
2476     }
2477     return true;
2478 }
2479 
2480 void SvnActions::checkAddItems(const QString &path, bool print_error_box)
2481 {
2482     svn::StatusEntries dlist;
2483     svn::StatusEntries rlist;
2484     QStringList displist;
2485     svn::Revision where = svn::Revision::HEAD;
2486     if (!makeStatus(path, dlist, where, true, true, false, false)) {
2487         return;
2488     }
2489     for (const auto &entry : qAsConst(dlist)) {
2490         if (!entry->isVersioned()) {
2491             rlist.append(entry);
2492             displist.append(entry->path());
2493         }
2494     }
2495     if (rlist.isEmpty()) {
2496         if (print_error_box) {
2497             KMessageBox::error(m_Data->m_ParentList->realWidget(), i18n("No unversioned items found."));
2498         }
2499     } else {
2500         QPointer<KSvnSimpleOkDialog> dlg(new KSvnSimpleOkDialog(QStringLiteral("add_items_dlg")));
2501         dlg->setWindowTitle(i18nc("@title:window", "Add Unversioned Items"));
2502         dlg->setWithCancelButton();
2503         QTreeWidget *ptr(new QTreeWidget(dlg));
2504         ptr->headerItem()->setText(0, i18n("Item"));
2505         for (const QString &text : qAsConst(displist)) {
2506             QTreeWidgetItem *n = new QTreeWidgetItem(ptr);
2507             n->setText(0, text);
2508             n->setCheckState(0, Qt::Checked);
2509         }
2510         ptr->resizeColumnToContents(0);
2511         dlg->addWidget(ptr);
2512         if (dlg->exec() == QDialog::Accepted) {
2513             QTreeWidgetItemIterator it(ptr);
2514             displist.clear();
2515             while (*it) {
2516                 QTreeWidgetItem *t = (*it);
2517                 if (t->checkState(0) == Qt::Checked) {
2518                     displist.append(t->text(0));
2519                 }
2520                 ++it;
2521             }
2522             if (!displist.isEmpty()) {
2523                 addItems(svn::Targets::fromStringList(displist).targets(), svn::DepthEmpty);
2524             }
2525         }
2526         delete dlg;
2527     }
2528 }
2529 
2530 void SvnActions::stopCheckModifiedThread()
2531 {
2532     if (m_CThread) {
2533         m_CThread->cancelMe();
2534         if (!m_CThread->wait(MAX_THREAD_WAITTIME)) {
2535             m_CThread->terminate();
2536             m_CThread->wait(MAX_THREAD_WAITTIME);
2537         }
2538         delete m_CThread;
2539         m_CThread = nullptr;
2540     }
2541 }
2542 
2543 void SvnActions::stopCheckUpdateThread()
2544 {
2545     if (m_UThread) {
2546         m_UThread->cancelMe();
2547         if (!m_UThread->wait(MAX_THREAD_WAITTIME)) {
2548             m_UThread->terminate();
2549             m_UThread->wait(MAX_THREAD_WAITTIME);
2550         }
2551         delete m_UThread;
2552         m_UThread = nullptr;
2553     }
2554 }
2555 
2556 void SvnActions::stopFillCache()
2557 {
2558     if (m_FCThread) {
2559         m_FCThread->cancelMe();
2560         if (!m_FCThread->wait(MAX_THREAD_WAITTIME)) {
2561             m_FCThread->terminate();
2562             m_FCThread->wait(MAX_THREAD_WAITTIME);
2563         }
2564         delete m_FCThread;
2565         m_FCThread = nullptr;
2566         emit sigThreadsChanged();
2567         emit sigCacheStatus(-1, -1);
2568     }
2569 }
2570 
2571 void SvnActions::stopMain()
2572 {
2573     if (m_Data->m_CurrentContext) {
2574         m_Data->m_SvnContextListener->setCanceled(true);
2575         sleep(1);
2576         m_Data->m_SvnContextListener->contextCancel();
2577     }
2578 }
2579 
2580 void SvnActions::killallThreads()
2581 {
2582     stopMain();
2583     stopCheckModifiedThread();
2584     stopCheckUpdateThread();
2585     stopFillCache();
2586 }
2587 
2588 bool SvnActions::createModifiedCache(const QString &what)
2589 {
2590     stopCheckModifiedThread();
2591     m_CThread = new CheckModifiedThread(this, what, false);
2592     connect(m_CThread, &CheckModifiedThread::checkModifiedFinished, this, &SvnActions::checkModifiedThread);
2593     m_CThread->start();
2594     return true;
2595 }
2596 
2597 void SvnActions::checkModifiedThread()
2598 {
2599     if (!m_CThread) {
2600         return;
2601     }
2602     if (m_CThread->isRunning()) {
2603         QTimer::singleShot(2, this, &SvnActions::checkModifiedThread);
2604         return;
2605     }
2606     m_Data->m_Cache.clear();
2607     m_Data->m_conflictCache.clear();
2608     const svn::StatusEntries &sEntries = m_CThread->getList();
2609     for (const auto &ptr : sEntries) {
2610         if (ptr->isRealVersioned()
2611             && (ptr->nodeStatus() == svn_wc_status_modified || ptr->nodeStatus() == svn_wc_status_added || ptr->nodeStatus() == svn_wc_status_deleted
2612                 || ptr->nodeStatus() == svn_wc_status_replaced || ptr->nodeStatus() == svn_wc_status_modified)) {
2613             m_Data->m_Cache.insertKey(ptr, ptr->path());
2614         } else if (ptr->nodeStatus() == svn_wc_status_conflicted) {
2615             m_Data->m_conflictCache.insertKey(ptr, ptr->path());
2616         }
2617         emit sigRefreshItem(ptr->path());
2618     }
2619     emit sigExtraStatusMessage(i18np("Found %1 modified item", "Found %1 modified items", sEntries.size()));
2620     delete m_CThread;
2621     m_CThread = nullptr;
2622     emit sigCacheDataChanged();
2623 }
2624 
2625 void SvnActions::checkUpdateThread()
2626 {
2627     if (!m_UThread || m_UThread->isRunning()) {
2628         if (m_UThread) {
2629             QTimer::singleShot(2, this, &SvnActions::checkUpdateThread);
2630         }
2631         return;
2632     }
2633     bool newer = false;
2634     const svn::StatusEntries &sEntries = m_UThread->getList();
2635     for (const auto &ptr : sEntries) {
2636         if (ptr->validReposStatus()) {
2637             m_Data->m_UpdateCache.insertKey(ptr, ptr->path());
2638             if (!(ptr->validLocalStatus())) {
2639                 newer = true;
2640             }
2641         }
2642         if (ptr->isLocked() && !(ptr->entry().lockEntry().Locked())) {
2643             m_Data->m_repoLockCache.insertKey(ptr, ptr->path());
2644         }
2645         emit sigRefreshItem(ptr->path());
2646     }
2647     emit sigExtraStatusMessage(i18n("Checking for updates finished"));
2648     if (newer) {
2649         emit sigExtraStatusMessage(i18n("There are new items in repository"));
2650     }
2651     delete m_UThread;
2652     m_UThread = nullptr;
2653     emit sigCacheDataChanged();
2654 }
2655 
2656 void SvnActions::getaddedItems(const QString &path, svn::StatusEntries &target)
2657 {
2658     helpers::ValidRemoteOnly vro;
2659     m_Data->m_UpdateCache.listsubs_if(path, vro);
2660     target = vro.liste();
2661 }
2662 
2663 bool SvnActions::checkUpdatesRunning()
2664 {
2665     return m_UThread && m_UThread->isRunning();
2666 }
2667 
2668 void SvnActions::addModifiedCache(const svn::StatusPtr &what)
2669 {
2670     if (what->nodeStatus() == svn_wc_status_conflicted) {
2671         m_Data->m_conflictCache.insertKey(what, what->path());
2672         emit sigRefreshItem(what->path());
2673     } else {
2674         m_Data->m_Cache.insertKey(what, what->path());
2675     }
2676 }
2677 
2678 void SvnActions::deleteFromModifiedCache(const QString &what)
2679 {
2680     m_Data->m_Cache.deleteKey(what, true);
2681     m_Data->m_conflictCache.deleteKey(what, true);
2682     // m_Data->m_Cache.dump_tree();
2683     emit sigRefreshItem(what);
2684 }
2685 
2686 bool SvnActions::checkModifiedCache(const QString &path) const
2687 {
2688     return m_Data->m_Cache.find(path);
2689 }
2690 
2691 bool SvnActions::checkReposLockCache(const QString &path) const
2692 {
2693     return m_Data->m_repoLockCache.findSingleValid(path, false);
2694 }
2695 
2696 bool SvnActions::checkReposLockCache(const QString &path, svn::StatusPtr &t) const
2697 {
2698     /// @todo create a method where svn::Status* will be a parameter so no copy is needed but just reading content
2699     return m_Data->m_repoLockCache.findSingleValid(path, t);
2700 }
2701 
2702 bool SvnActions::checkConflictedCache(const QString &path) const
2703 {
2704     return m_Data->m_conflictCache.find(path);
2705 }
2706 
2707 void SvnActions::startFillCache(const QString &path, bool startup)
2708 {
2709 #ifdef DEBUG_TIMER
2710     QTime _counttime;
2711     _counttime.start();
2712 #endif
2713     stopFillCache();
2714 #ifdef DEBUG_TIMER
2715     qCDebug(KDESVN_LOG) << "Stopped cache " << _counttime.elapsed();
2716     _counttime.restart();
2717 #endif
2718     if (!doNetworking()) {
2719         emit sendNotify(i18n("Not filling log cache because networking is disabled"));
2720         return;
2721     }
2722 
2723     m_FCThread = new FillCacheThread(this, path, startup);
2724     connect(m_FCThread, &FillCacheThread::fillCacheStatus, this, &SvnActions::sigCacheStatus);
2725     connect(m_FCThread, &FillCacheThread::fillCacheFinished, this, &SvnActions::stopFillCache);
2726     m_FCThread->start();
2727 }
2728 
2729 bool SvnActions::doNetworking()
2730 {
2731     // if networking is allowd we don't need extra checks, second is just for avoiding segfaults
2732     if (Kdesvnsettings::network_on() || !m_Data->m_ParentList) {
2733         return true;
2734     }
2735     bool is_url = false;
2736     if (m_Data->m_ParentList->isNetworked()) {
2737         // if called http:// etc.pp.
2738         is_url = true;
2739     } else if (m_Data->m_ParentList->baseUri().startsWith(QLatin1Char('/'))) {
2740         // if opened a working copy we must check if it points to a networking repository
2741         svn::InfoEntry e;
2742         if (!singleInfo(m_Data->m_ParentList->baseUri(), svn::Revision::UNDEFINED, e)) {
2743             return false;
2744         }
2745         is_url = !e.reposRoot().isLocalFile();
2746     }
2747     return !is_url;
2748 }
2749 
2750 /*!
2751     \fn SvnActions::createUpdateCache(const QString&what)
2752  */
2753 bool SvnActions::createUpdateCache(const QString &what)
2754 {
2755     clearUpdateCache();
2756     m_Data->m_repoLockCache.clear();
2757     stopCheckUpdateThread();
2758     if (!doNetworking()) {
2759         emit sigExtraStatusMessage(i18n("Not checking for updates because networking is disabled"));
2760         return false;
2761     }
2762     m_UThread = new CheckModifiedThread(this, what, true);
2763     connect(m_UThread, &CheckModifiedThread::checkModifiedFinished, this, &SvnActions::checkUpdateThread);
2764     m_UThread->start();
2765     emit sigExtraStatusMessage(i18n("Checking for updates started in background"));
2766     return true;
2767 }
2768 
2769 bool SvnActions::checkUpdateCache(const QString &path) const
2770 {
2771     return m_Data->m_UpdateCache.find(path);
2772 }
2773 
2774 void SvnActions::removeFromUpdateCache(const QStringList &what, bool exact_only)
2775 {
2776     for (const QString &key : what) {
2777         m_Data->m_UpdateCache.deleteKey(key, exact_only);
2778     }
2779 }
2780 
2781 bool SvnActions::isUpdated(const QString &path) const
2782 {
2783     svn::StatusPtr d;
2784     return getUpdated(path, d);
2785 }
2786 
2787 bool SvnActions::getUpdated(const QString &path, svn::StatusPtr &d) const
2788 {
2789     return m_Data->m_UpdateCache.findSingleValid(path, d);
2790 }
2791 
2792 void SvnActions::clearUpdateCache()
2793 {
2794     m_Data->m_UpdateCache.clear();
2795 }
2796 
2797 bool SvnActions::makeIgnoreEntry(const svn::Path &item, const QStringList &ignorePattern, bool unignore)
2798 {
2799     svn::Revision r(svn::Revision::UNDEFINED);
2800 
2801     QPair<qlonglong, svn::PathPropertiesMapList> pmp;
2802     try {
2803         pmp = m_Data->m_Svnclient->propget(QStringLiteral("svn:ignore"), item, r, r);
2804     } catch (const svn::Exception &e) {
2805         emit clientException(e.msg());
2806         return false;
2807     }
2808     svn::PathPropertiesMapList pm = pmp.second;
2809     QString data;
2810     if (!pm.isEmpty()) {
2811         const svn::PropertiesMap &mp = pm[0].second;
2812         data = mp[QStringLiteral("svn:ignore")];
2813     }
2814     bool result = false;
2815     QStringList lst = data.split(QLatin1Char('\n'), QString::SkipEmptyParts);
2816 
2817     for (const QString &ignore : ignorePattern) {
2818         int it = lst.indexOf(ignore);
2819         if (it != -1) {
2820             if (unignore) {
2821                 lst.removeAt(it);
2822                 result = true;
2823             }
2824         } else {
2825             if (!unignore) {
2826                 lst.append(ignore);
2827                 result = true;
2828             }
2829         }
2830     }
2831     if (result) {
2832         data = lst.join(QLatin1Char('\n'));
2833         try {
2834             m_Data->m_Svnclient->propset(svn::PropertiesParameter().propertyName(QStringLiteral("svn:ignore")).propertyValue(data).path(item));
2835         } catch (const svn::Exception &e) {
2836             emit clientException(e.msg());
2837             return false;
2838         }
2839     }
2840     return result;
2841 }
2842 
2843 bool SvnActions::makeIgnoreEntry(SvnItem *which, bool unignore)
2844 {
2845     if (!which) {
2846         return false;
2847     }
2848     QString parentName = which->getParentDir();
2849     if (parentName.isEmpty()) {
2850         return false;
2851     }
2852     QString name = which->shortName();
2853     return makeIgnoreEntry(svn::Path(parentName), QStringList(name), unignore);
2854 }
2855 
2856 svn::PathPropertiesMapListPtr SvnActions::propList(const QString &which, const svn::Revision &where, bool cacheOnly)
2857 {
2858     svn::PathPropertiesMapListPtr pm;
2859     if (!which.isEmpty()) {
2860         QString fk = where.toString() + QLatin1Char('/') + which;
2861         svn::Path p(which);
2862 
2863         if (where != svn::Revision::WORKING) {
2864             m_Data->m_PropertiesCache.findSingleValid(fk, pm);
2865         }
2866         if (!pm && !cacheOnly) {
2867             try {
2868                 pm = m_Data->m_Svnclient->proplist(p, where, where);
2869             } catch (const svn::Exception &e) {
2870                 /* no messagebox needed */
2871                 if (e.apr_err() != SVN_ERR_WC_NOT_DIRECTORY) {
2872                     emit sendNotify(e.msg());
2873                 }
2874             }
2875             if (where != svn::Revision::WORKING && pm) {
2876                 m_Data->m_PropertiesCache.insertKey(pm, fk);
2877             }
2878         }
2879     }
2880     return pm;
2881 }
2882 
2883 bool SvnActions::isLockNeeded(SvnItem *which, const svn::Revision &where)
2884 {
2885     if (!which) {
2886         return false;
2887     }
2888     svn::Path p(which->fullName());
2889 
2890     QPair<qlonglong, svn::PathPropertiesMapList> pmp;
2891     try {
2892         pmp = m_Data->m_Svnclient->propget(QStringLiteral("svn:needs-lock"), p, where, where);
2893     } catch (const svn::Exception &) {
2894         /* no messagebox needed */
2895         // emit clientException(e.msg());
2896         return false;
2897     }
2898     const svn::PathPropertiesMapList pm = pmp.second;
2899     if (!pm.isEmpty()) {
2900         const svn::PropertiesMap &mp = pm.at(0).second;
2901         if (mp.contains(QStringLiteral("svn:needs-lock"))) {
2902             return true;
2903         }
2904     }
2905     return false;
2906 }
2907 
2908 QString SvnActions::searchProperty(QString &Store, const QString &property, const QString &start, const svn::Revision &where, bool up)
2909 {
2910     svn::Path pa(start);
2911     svn::InfoEntry inf;
2912 
2913     if (!singleInfo(start, where, inf)) {
2914         return QString();
2915     }
2916     while (pa.length() > 0) {
2917         const svn::PathPropertiesMapListPtr pm = propList(pa.path(), where, false);
2918         if (!pm) {
2919             return QString();
2920         }
2921         if (!pm->isEmpty()) {
2922             const svn::PropertiesMap &mp = pm->at(0).second;
2923             const svn::PropertiesMap::ConstIterator it = mp.find(property);
2924             if (it != mp.end()) {
2925                 Store = *it;
2926                 return pa.path();
2927             }
2928         }
2929         if (up) {
2930             pa.removeLast();
2931             if (pa.isUrl() && inf.reposRoot().toString().length() > pa.path().length()) {
2932                 break;
2933             }
2934 
2935         } else {
2936             break;
2937         }
2938     }
2939     return QString();
2940 }
2941 
2942 bool SvnActions::makeList(const QString &url, svn::DirEntries &dlist, const svn::Revision &where, svn::Depth depth)
2943 {
2944     if (!m_Data->m_CurrentContext) {
2945         return false;
2946     }
2947     try {
2948         dlist = m_Data->m_Svnclient->list(url, where, where, depth, false);
2949     } catch (const svn::Exception &e) {
2950         qCDebug(KDESVN_LOG) << "List fehler: " << e.msg();
2951         emit clientException(e.msg());
2952         return false;
2953     }
2954     return true;
2955 }
2956 
2957 bool SvnActions::isLocalWorkingCopy(const QString &path, QUrl &repoUrl)
2958 {
2959     if (path.isEmpty()) {
2960         return false;
2961     }
2962     const QUrl url = helpers::KTranslateUrl::string2Uri(path);
2963     if (!url.isLocalFile()) {
2964         qCDebug(KDESVN_LOG) << "isLocalWorkingCopy no local file: " << path << " - " << url.toString();
2965         return false;
2966     }
2967 
2968     QString cleanpath = url.adjusted(QUrl::StripTrailingSlash | QUrl::NormalizePathSegments).path();
2969     qCDebug(KDESVN_LOG) << "isLocalWorkingCopy for " << cleanpath;
2970     repoUrl.clear();
2971     svn::Revision peg(svn_opt_revision_unspecified);
2972     svn::Revision rev(svn_opt_revision_unspecified);
2973     svn::InfoEntries e;
2974     try {
2975         e = m_Data->m_Svnclient->info(cleanpath, svn::DepthEmpty, rev, peg);
2976     } catch (const svn::Exception &e) {
2977         if (SVN_ERR_WC_NOT_DIRECTORY == e.apr_err()) {
2978             return false;
2979         }
2980         return true;
2981     }
2982     if (!e.isEmpty())
2983         repoUrl = e.at(0).url();
2984     return true;
2985 }
2986 
2987 void SvnActions::slotExtraLogMsg(const QString &msg)
2988 {
2989     emit sigExtraLogMsg(msg);
2990 }
2991 
2992 void SvnActions::slotCancel(bool how)
2993 {
2994     if (!m_Data->m_CurrentContext) {
2995         return;
2996     }
2997     m_Data->m_SvnContextListener->setCanceled(how);
2998 }
2999 
3000 void SvnActions::setContextData(const QString &aKey, const QString &aValue)
3001 {
3002     if (aValue.isNull()) {
3003         m_Data->m_contextData.remove(aKey);
3004     } else {
3005         m_Data->m_contextData[aKey] = aValue;
3006     }
3007 }
3008 
3009 void SvnActions::clearContextData()
3010 {
3011     m_Data->m_contextData.clear();
3012 }
3013 
3014 QString SvnActions::getContextData(const QString &aKey) const
3015 {
3016     if (m_Data->m_contextData.find(aKey) != m_Data->m_contextData.end()) {
3017         return m_Data->m_contextData[aKey];
3018     }
3019     return QString();
3020 }
3021 
3022 bool SvnActions::threadRunning(ThreadType which) const
3023 {
3024     switch (which) {
3025     case checkupdatethread:
3026         return (m_UThread && m_UThread->isRunning());
3027     case fillcachethread:
3028         return (m_FCThread && m_FCThread->isRunning());
3029     case checkmodifiedthread:
3030         return (m_CThread && m_CThread->isRunning());
3031     }
3032     return false;
3033 }
3034 
3035 #include "moc_svnactions.cpp"