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