File indexing completed on 2024-05-12 17:16:16

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