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