File indexing completed on 2024-04-14 05:34:14

0001 /*
0002     SPDX-FileCopyrightText: 2011 Vishesh Yadav <vishesh3y@gmail.com>
0003     SPDX-FileCopyrightText: 2015 Tomasz Bojczuk <seelook@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "fileviewhgplugin.h"
0009 #include "hgconfig.h"
0010 #include "configdialog.h"
0011 #include "renamedialog.h"
0012 #include "commitdialog.h"
0013 #include "branchdialog.h"
0014 #include "tagdialog.h"
0015 #include "updatedialog.h"
0016 #include "clonedialog.h"
0017 #include "createdialog.h"
0018 #include "pushdialog.h"
0019 #include "pulldialog.h"
0020 #include "mergedialog.h"
0021 #include "bundledialog.h"
0022 #include "exportdialog.h"
0023 #include "importdialog.h"
0024 #include "servedialog.h"
0025 #include "backoutdialog.h"
0026 
0027 #include <QTextCodec>
0028 #include <QDir>
0029 #include <QAction>
0030 #include <QIcon>
0031 #include <QMenu>
0032 #include <QDebug>
0033 
0034 #include <KApplicationTrader>
0035 #include <KMessageBox>
0036 #include <QFileDialog>
0037 #include <KConfig>
0038 #include <KConfigGroup>
0039 #include <KService>
0040 
0041 #include <KLocalizedString>
0042 #include <KPluginFactory>
0043 #include <QRegularExpression>
0044 
0045 K_PLUGIN_CLASS_WITH_JSON(FileViewHgPlugin, "fileviewhgplugin.json")
0046 
0047 //TODO: Build a proper status signal system to sync HgWrapper/Dialogs with this
0048 //TODO: Show error messages and set their message appropriately(hg output)
0049 //TODO: Use xi18nc rather than i18c throughout plugin
0050 
0051 FileViewHgPlugin::FileViewHgPlugin(QObject *parent, const QList<QVariant> &args):
0052     KVersionControlPlugin(parent),
0053     m_mainContextMenu(nullptr),
0054     m_addAction(nullptr),
0055     m_removeAction(nullptr),
0056     m_renameAction(nullptr),
0057     m_commitAction(nullptr),
0058     m_branchAction(nullptr),
0059     m_tagAction(nullptr),
0060     m_updateAction(nullptr),
0061     m_cloneAction(nullptr),
0062     m_createAction(nullptr),
0063     m_configAction(nullptr),
0064     m_globalConfigAction(nullptr),
0065     m_repoConfigAction(nullptr),
0066     m_pushAction(nullptr),
0067     m_pullAction(nullptr),
0068     m_revertAction(nullptr),
0069     m_revertAllAction(nullptr),
0070     m_rollbackAction(nullptr),
0071     m_mergeAction(nullptr),
0072     m_bundleAction(nullptr),
0073     m_exportAction(nullptr),
0074     m_unbundleAction(nullptr),
0075     m_importAction(nullptr),
0076     m_diffAction(nullptr),
0077     m_serveAction(nullptr),
0078     m_backoutAction(nullptr),
0079     m_isCommitable(false),
0080     m_hgWrapper(nullptr),
0081     m_retrievalHgw(nullptr)
0082 {
0083     Q_UNUSED(args);
0084 
0085     qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
0086     qRegisterMetaType<QProcess::ExitStatus>("QProcess::ExitStatus");
0087     qRegisterMetaType<QProcess::ProcessState>("QProcess::ProcessState");
0088 
0089     m_parentWidget = qobject_cast<QWidget*>(parent);
0090 
0091     m_addAction = new QAction(this);
0092     m_addAction->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0093     m_addAction->setText(xi18nc("@action:inmenu",
0094                                "<application>Hg</application> Add"));
0095     connect(m_addAction, &QAction::triggered,
0096             this, &FileViewHgPlugin::addFiles);
0097 
0098     m_removeAction = new QAction(this);
0099     m_removeAction->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
0100     m_removeAction->setText(xi18nc("@action:inmenu",
0101                                   "<application>Hg</application> Remove"));
0102     connect(m_removeAction, &QAction::triggered,
0103             this, &FileViewHgPlugin::removeFiles);
0104 
0105     m_renameAction = new QAction(this);
0106     m_renameAction->setIcon(QIcon::fromTheme(QStringLiteral("list-rename")));
0107     m_renameAction->setText(xi18nc("@action:inmenu",
0108                                   "<application>Hg</application> Rename"));
0109     connect(m_renameAction, &QAction::triggered,
0110             this, &FileViewHgPlugin::renameFile);
0111 
0112     m_commitAction = new QAction(this);
0113     m_commitAction->setIcon(QIcon::fromTheme(QStringLiteral("vcs-commit")));
0114     m_commitAction->setText(xi18nc("@action:inmenu",
0115                                   "<application>Hg</application> Commit"));
0116     connect(m_commitAction, &QAction::triggered,
0117             this, &FileViewHgPlugin::commit);
0118 
0119     m_tagAction = new QAction(this);
0120     m_tagAction->setIcon(QIcon::fromTheme(QStringLiteral("svn-tag")));
0121     m_tagAction->setText(xi18nc("@action:inmenu",
0122                                   "<application>Hg</application> Tag"));
0123     connect(m_tagAction, &QAction::triggered,
0124             this, &FileViewHgPlugin::tag);
0125 
0126     m_branchAction = new QAction(this);
0127     m_branchAction->setIcon(QIcon::fromTheme(QStringLiteral("vcs-branch")));
0128     m_branchAction->setText(xi18nc("@action:inmenu",
0129                                   "<application>Hg</application> Branch"));
0130     connect(m_branchAction, &QAction::triggered,
0131             this, &FileViewHgPlugin::branch);
0132 
0133     m_cloneAction = new QAction(this);
0134     m_cloneAction->setIcon(QIcon::fromTheme(QStringLiteral("hg-clone")));
0135     m_cloneAction->setText(xi18nc("@action:inmenu",
0136                                   "<application>Hg</application> Clone"));
0137     connect(m_cloneAction, &QAction::triggered,
0138             this, &FileViewHgPlugin::clone);
0139 
0140     m_createAction = new QAction(this);
0141     m_createAction->setIcon(QIcon::fromTheme(QStringLiteral("hg-create")));
0142     m_createAction->setText(xi18nc("@action:inmenu",
0143                                   "<application>Hg</application> Init"));
0144     connect(m_createAction, &QAction::triggered,
0145             this, &FileViewHgPlugin::create);
0146 
0147     m_updateAction = new QAction(this);
0148     m_updateAction->setIcon(QIcon::fromTheme(QStringLiteral("vcs-pull")));
0149     m_updateAction->setText(xi18nc("@action:inmenu",
0150                                   "<application>Hg</application> Update"));
0151     connect(m_updateAction, &QAction::triggered,
0152             this, &FileViewHgPlugin::update);
0153 
0154     m_globalConfigAction = new QAction(this);
0155     m_globalConfigAction->setIcon(QIcon::fromTheme(QStringLiteral("hg-config")));
0156     m_globalConfigAction->setText(xi18nc("@action:inmenu",
0157                           "<application>Hg</application> Global Config"));
0158     connect(m_globalConfigAction, &QAction::triggered,
0159             this, &FileViewHgPlugin::global_config);
0160 
0161     m_repoConfigAction = new QAction(this);
0162     m_repoConfigAction->setIcon(QIcon::fromTheme(QStringLiteral("hg-config")));
0163     m_repoConfigAction->setText(xi18nc("@action:inmenu",
0164                       "<application>Hg</application> Repository Config"));
0165     connect(m_repoConfigAction, &QAction::triggered,
0166             this, &FileViewHgPlugin::repo_config);
0167 
0168     m_pushAction = new QAction(this);
0169     m_pushAction->setIcon(QIcon::fromTheme(QStringLiteral("vcs-push")));
0170     m_pushAction->setText(xi18nc("@action:inmenu",
0171                                   "<application>Hg</application> Push"));
0172     connect(m_pushAction, &QAction::triggered,
0173             this, &FileViewHgPlugin::push);
0174 
0175     m_pullAction = new QAction(this);
0176     m_pullAction->setIcon(QIcon::fromTheme(QStringLiteral("vcs-pull")));
0177     m_pullAction->setText(xi18nc("@action:inmenu",
0178                                   "<application>Hg</application> Pull"));
0179     connect(m_pullAction, &QAction::triggered,
0180             this, &FileViewHgPlugin::pull);
0181 
0182     m_revertAction = new QAction(this);
0183     m_revertAction->setIcon(QIcon::fromTheme(QStringLiteral("document-revert")));
0184     m_revertAction->setText(xi18nc("@action:inmenu",
0185                                   "<application>Hg</application> Revert"));
0186     connect(m_revertAction, &QAction::triggered,
0187             this, &FileViewHgPlugin::revert);
0188 
0189     m_revertAllAction = new QAction(this);
0190     m_revertAllAction->setIcon(QIcon::fromTheme(QStringLiteral("document-revert")));
0191     m_revertAllAction->setText(xi18nc("@action:inmenu",
0192                                  "<application>Hg</application> Revert All"));
0193     connect(m_revertAllAction, &QAction::triggered,
0194             this, &FileViewHgPlugin::revertAll);
0195 
0196     m_rollbackAction = new QAction(this);
0197     m_rollbackAction->setIcon(QIcon::fromTheme(QStringLiteral("hg-rollback")));
0198     m_rollbackAction->setText(xi18nc("@action:inmenu",
0199                                  "<application>Hg</application> Rollback"));
0200     connect(m_rollbackAction, &QAction::triggered,
0201             this, &FileViewHgPlugin::rollback);
0202 
0203     m_mergeAction = new QAction(this);
0204     m_mergeAction->setIcon(QIcon::fromTheme(QStringLiteral("vcs-merge")));
0205     m_mergeAction->setText(xi18nc("@action:inmenu",
0206                                  "<application>Hg</application> Merge"));
0207     connect(m_mergeAction, &QAction::triggered,
0208             this, &FileViewHgPlugin::merge);
0209 
0210     m_bundleAction = new QAction(this);
0211     m_bundleAction->setIcon(QIcon::fromTheme(QStringLiteral("hg-bundle")));
0212     m_bundleAction->setText(xi18nc("@action:inmenu",
0213                                  "<application>Hg</application> Bundle"));
0214     connect(m_bundleAction, &QAction::triggered,
0215             this, &FileViewHgPlugin::bundle);
0216 
0217     m_exportAction = new QAction(this);
0218     m_exportAction->setIcon(QIcon::fromTheme(QStringLiteral("hg-export")));
0219     m_exportAction->setText(xi18nc("@action:inmenu",
0220                                  "<application>Hg</application> Export"));
0221     connect(m_exportAction, &QAction::triggered,
0222             this, &FileViewHgPlugin::exportChangesets);
0223 
0224     m_importAction = new QAction(this);
0225     m_importAction->setIcon(QIcon::fromTheme(QStringLiteral("hg-import")));
0226     m_importAction->setText(xi18nc("@action:inmenu",
0227                                  "<application>Hg</application> Import"));
0228     connect(m_importAction, &QAction::triggered,
0229             this, &FileViewHgPlugin::importChangesets);
0230 
0231     m_unbundleAction = new QAction(this);
0232     m_unbundleAction->setIcon(QIcon::fromTheme(QStringLiteral("hg-unbundle")));
0233     m_unbundleAction->setText(xi18nc("@action:inmenu",
0234                                  "<application>Hg</application> Unbundle"));
0235     connect(m_unbundleAction, &QAction::triggered,
0236             this, &FileViewHgPlugin::unbundle);
0237 
0238     m_serveAction = new QAction(this);
0239     m_serveAction->setIcon(QIcon::fromTheme(QStringLiteral("hg-serve")));
0240     m_serveAction->setText(xi18nc("@action:inmenu",
0241                                  "<application>Hg</application> Serve"));
0242     connect(m_serveAction, &QAction::triggered,
0243             this, &FileViewHgPlugin::serve);
0244 
0245     m_backoutAction = new QAction(this);
0246     m_backoutAction->setIcon(QIcon::fromTheme(QStringLiteral("hg-backout")));
0247     m_backoutAction->setText(xi18nc("@action:inmenu",
0248                                  "<application>Hg</application> Backout"));
0249     connect(m_backoutAction, &QAction::triggered,
0250             this, &FileViewHgPlugin::backout);
0251 
0252     m_diffAction = new QAction(this);
0253     m_diffAction->setIcon(QIcon::fromTheme(QStringLiteral("vcs-diff")));
0254     m_diffAction->setText(xi18nc("@action:inmenu",
0255                                  "<application>Hg</application> Diff"));
0256     connect(m_diffAction, &QAction::triggered,
0257             this, &FileViewHgPlugin::diff);
0258 
0259     /* Submenu to make the main menu less cluttered */
0260     m_mainContextMenu = new QMenu;
0261     m_mainContextMenu->addAction(m_updateAction);
0262     m_mainContextMenu->addAction(m_branchAction);
0263     m_mainContextMenu->addAction(m_tagAction);
0264     m_mainContextMenu->addAction(m_mergeAction);
0265     m_mainContextMenu->addAction(m_revertAllAction);
0266     m_mainContextMenu->addAction(m_rollbackAction);
0267     m_mainContextMenu->addAction(m_backoutAction);
0268     m_mainContextMenu->addAction(m_bundleAction);
0269     m_mainContextMenu->addAction(m_unbundleAction);
0270     m_mainContextMenu->addAction(m_exportAction);
0271     m_mainContextMenu->addAction(m_importAction);
0272     m_mainContextMenu->addAction(m_serveAction);
0273     m_mainContextMenu->addAction(m_globalConfigAction);
0274     m_mainContextMenu->addAction(m_repoConfigAction);
0275 
0276     m_menuAction = new QAction(this);
0277     m_menuAction->setIcon(QIcon::fromTheme(QStringLiteral("hg-main")));
0278     m_menuAction->setText(xi18nc("@action:inmenu",
0279                                   "<application>Mercurial</application>"));
0280     m_menuAction->setMenu(m_mainContextMenu);
0281 }
0282 
0283 FileViewHgPlugin::~FileViewHgPlugin()
0284 {
0285 }
0286 
0287 void FileViewHgPlugin::createHgWrapper() const
0288 {
0289     static bool created = false;
0290 
0291     if (created && m_hgWrapper != nullptr) {
0292         return;
0293     }
0294 
0295     created = true;
0296 
0297     m_hgWrapper = HgWrapper::instance();
0298 
0299     connect(m_hgWrapper, &HgWrapper::primaryOperationFinished,
0300             this, &FileViewHgPlugin::slotOperationCompleted);
0301     connect(m_hgWrapper, &HgWrapper::primaryOperationError,
0302             this, &FileViewHgPlugin::slotOperationError);
0303 }
0304 
0305 QString FileViewHgPlugin::fileName() const
0306 {
0307     return QStringLiteral(".hg");
0308 }
0309 
0310 QString FileViewHgPlugin::localRepositoryRoot(const QString& directory) const
0311 {
0312     QProcess process;
0313     process.setWorkingDirectory(directory);
0314     process.start(QStringLiteral("hg"), {QStringLiteral("root")});
0315     if (process.waitForReadyRead(100) && process.exitCode() == 0) {
0316         return QString::fromUtf8(process.readAll().chopped(1));
0317     }
0318     return QString();
0319 }
0320 
0321 bool FileViewHgPlugin::beginRetrieval(const QString &directory)
0322 {
0323     clearMessages();
0324     m_currentDir = directory;
0325     m_versionInfoHash.clear();
0326     //createHgWrapper();
0327     //m_hgWrapper->setCurrentDir(directory);
0328     //m_hgWrapper->getItemVersions(m_versionInfoHash);
0329     if (m_retrievalHgw == nullptr) {
0330         m_retrievalHgw = new HgWrapper;
0331     }
0332     m_retrievalHgw->setCurrentDir(directory);
0333     m_retrievalHgw->getItemVersions(m_versionInfoHash);
0334     return true;
0335 }
0336 
0337 void FileViewHgPlugin::endRetrieval()
0338 {
0339 }
0340 
0341 KVersionControlPlugin::ItemVersion FileViewHgPlugin::itemVersion(const KFileItem &item) const
0342 {
0343     //FIXME: When folder is empty or all files within untracked.
0344     const QString itemUrl = item.localPath();
0345     if (item.isDir()) {
0346         QHash<QString, ItemVersion>::const_iterator it
0347                                     = m_versionInfoHash.constBegin();
0348         while (it != m_versionInfoHash.constEnd()) {
0349             if (it.key().startsWith(itemUrl)) {
0350                 const ItemVersion state = m_versionInfoHash.value(it.key());
0351                 if (state == LocallyModifiedVersion ||
0352                         state == AddedVersion ||
0353                         state == RemovedVersion) {
0354                     return LocallyModifiedVersion;
0355                 }
0356             }
0357             ++it;
0358         }
0359 
0360         // Making folders with all files within untracked 'Unversioned'
0361         // will disable the context menu there. Will enable recursive
0362         // add however.
0363         QDir dir(item.localPath());
0364         const QStringList filesInside = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot);
0365         for (const QString &fileName : filesInside) {
0366             const QUrl tempUrl(dir.absoluteFilePath(fileName));
0367             KFileItem tempFileItem(tempUrl);
0368             if (itemVersion(tempFileItem) == NormalVersion) {
0369                return NormalVersion;
0370           }
0371         }
0372         return UnversionedVersion;
0373     }
0374     if (m_versionInfoHash.contains(itemUrl)) {
0375         return m_versionInfoHash.value(itemUrl);
0376     }
0377     return NormalVersion;
0378 }
0379 
0380 QList<QAction*> FileViewHgPlugin::versionControlActions(const KFileItemList &items) const
0381 {
0382     //TODO: Make it work with universal context menu when implemented
0383     //      in dolphin
0384     qDebug() << items.count();
0385     if (items.count() == 1 && items.first().isDir()) {
0386         return directoryContextMenu(m_currentDir);
0387     }
0388     else {
0389         return itemContextMenu(items);
0390     }
0391     return QList<QAction*>();
0392 }
0393 
0394 QList<QAction*> FileViewHgPlugin::outOfVersionControlActions(const KFileItemList &items) const
0395 {
0396     Q_UNUSED(items)
0397 
0398     return {};
0399 }
0400 
0401 QList<QAction*> FileViewHgPlugin::universalContextMenuActions(const QString &directory) const
0402 {
0403     QList<QAction*> result;
0404     m_universalCurrentDirectory = directory;
0405     result.append(m_createAction);
0406     result.append(m_cloneAction);
0407     return result;
0408 }
0409 
0410 QList<QAction*> FileViewHgPlugin::itemContextMenu(const KFileItemList &items) const
0411 {
0412     Q_ASSERT(!items.isEmpty());
0413 
0414     clearMessages();
0415     createHgWrapper();
0416     m_hgWrapper->setCurrentDir(m_currentDir);
0417     if (!m_hgWrapper->isBusy()) {
0418         m_contextItems.clear();
0419         for (const KFileItem &item : items) {
0420             m_contextItems.append(item);
0421         }
0422 
0423         //see which actions should be enabled
0424         int versionedCount = 0;
0425         int addableCount = 0;
0426         int revertableCount = 0;
0427         for (const KFileItem &item : items) {
0428             const ItemVersion state = itemVersion(item);
0429             if (state != UnversionedVersion && state != RemovedVersion) {
0430                 ++versionedCount;
0431             }
0432             if (state == UnversionedVersion ||
0433                     state == LocallyModifiedUnstagedVersion) {
0434                 ++addableCount;
0435             }
0436             if (state == LocallyModifiedVersion ||
0437                     state == AddedVersion ||
0438                     state == RemovedVersion) {
0439                 ++revertableCount;
0440             }
0441         }
0442 
0443         m_addAction->setEnabled(addableCount == items.count());
0444         m_removeAction->setEnabled(versionedCount == items.count());
0445         m_revertAction->setEnabled(revertableCount == items.count());
0446         m_diffAction->setEnabled(revertableCount == items.count() &&
0447                 items.size() == 1);
0448         m_renameAction->setEnabled(items.size() == 1 &&
0449                 itemVersion(items.first()) != UnversionedVersion);
0450     }
0451     else {
0452         m_addAction->setEnabled(false);
0453         m_removeAction->setEnabled(false);
0454         m_renameAction->setEnabled(false);
0455         m_revertAction->setEnabled(false);
0456         m_diffAction->setEnabled(false);
0457     }
0458 
0459     QList<QAction*> actions;
0460     actions.append(m_addAction);
0461     actions.append(m_removeAction);
0462     actions.append(m_renameAction);
0463     actions.append(m_revertAction);
0464     actions.append(m_diffAction);
0465 
0466     return actions;
0467 }
0468 
0469 QList<QAction*> FileViewHgPlugin::directoryContextMenu(const QString &directory) const
0470 {
0471     QList<QAction*> actions;
0472     clearMessages();
0473     createHgWrapper();
0474     m_hgWrapper->setCurrentDir(directory);
0475     if (!m_hgWrapper->isBusy()) {
0476         actions.append(m_commitAction);
0477     }
0478     actions.append(m_pushAction);
0479     actions.append(m_pullAction);
0480     actions.append(m_diffAction);
0481     actions.append(m_menuAction);
0482     return actions;
0483 }
0484 
0485 void FileViewHgPlugin::addFiles()
0486 {
0487     Q_ASSERT(!m_contextItems.isEmpty());
0488     QString infoMsg = xi18nc("@info:status",
0489          "Adding files to <application>Hg</application> repository...");
0490     m_errorMsg = xi18nc("@info:status",
0491          "Adding files to <application>Hg</application> repository failed.");
0492     m_operationCompletedMsg = xi18nc("@info:status",
0493          "Added files to <application>Hg</application> repository.");
0494 
0495     Q_EMIT infoMessage(infoMsg);
0496     m_hgWrapper->addFiles(m_contextItems);
0497     Q_EMIT itemVersionsChanged();
0498 }
0499 
0500 void FileViewHgPlugin::removeFiles()
0501 {
0502     Q_ASSERT(!m_contextItems.isEmpty());
0503 
0504     int answer = KMessageBox::questionTwoActions(nullptr,
0505                                             xi18nc("@message:yesorno",
0506                     "Would you like to remove selected files "
0507                     "from the repository?"), i18n("Remove Files"), KStandardGuiItem::remove(), KStandardGuiItem::cancel());
0508     if (answer == KMessageBox::ButtonCode::SecondaryAction) {
0509         return;
0510     }
0511 
0512     QString infoMsg = xi18nc("@info:status",
0513          "Removing files from <application>Hg</application> repository...");
0514     m_errorMsg = xi18nc("@info:status",
0515          "Removing files from <application>Hg</application> repository failed.");
0516     m_operationCompletedMsg = xi18nc("@info:status",
0517          "Removed files from <application>Hg</application> repository.");
0518 
0519     Q_EMIT infoMessage(infoMsg);
0520     m_hgWrapper->removeFiles(m_contextItems);
0521 }
0522 
0523 
0524 void FileViewHgPlugin::renameFile()
0525 {
0526     Q_ASSERT(m_contextItems.size() == 1);
0527 
0528     m_errorMsg = xi18nc("@info:status",
0529         "Renaming of file in <application>Hg</application> repository failed.");
0530     m_operationCompletedMsg = xi18nc("@info:status",
0531         "Renamed file in <application>Hg</application> repository successfully.");
0532     Q_EMIT infoMessage(xi18nc("@info:status",
0533         "Renaming file in <application>Hg</application> repository."));
0534 
0535     HgRenameDialog dialog(m_contextItems.first(), m_parentWidget);
0536     dialog.exec();
0537     m_contextItems.clear();
0538 }
0539 
0540 
0541 void FileViewHgPlugin::commit()
0542 {
0543     if (m_hgWrapper->isWorkingDirectoryClean()) {
0544         KMessageBox::information(nullptr, xi18nc("@message", "No changes for commit!"));
0545         return;
0546     }
0547     //FIXME: Disable emitting of status messages when executing sub tasks.
0548     m_errorMsg = xi18nc("@info:status",
0549             "Commit to <application>Hg</application> repository failed.");
0550     m_operationCompletedMsg = xi18nc("@info:status",
0551             "Committed to <application>Hg</application> repository.");
0552     Q_EMIT infoMessage(xi18nc("@info:status",
0553             "Commit <application>Hg</application> repository."));
0554 
0555     HgCommitDialog dialog(m_parentWidget);
0556     if (dialog.exec() == QDialog::Accepted) {
0557         Q_EMIT itemVersionsChanged();
0558     };
0559 }
0560 
0561 void FileViewHgPlugin::tag()
0562 {
0563     m_errorMsg = xi18nc("@info:status",
0564            "Tag operation in <application>Hg</application> repository failed.");
0565     m_operationCompletedMsg = xi18nc("@info:status",
0566            "Tagging operation in <application>Hg</application> repository is successful.");
0567     Q_EMIT infoMessage(xi18nc("@info:status",
0568            "Tagging operation in <application>Hg</application> repository."));
0569 
0570     HgTagDialog dialog(m_parentWidget);
0571     dialog.exec();
0572 }
0573 
0574 void FileViewHgPlugin::update()
0575 {
0576     m_errorMsg = xi18nc("@info:status",
0577            "Update of <application>Hg</application> working directory failed.");
0578     m_operationCompletedMsg = xi18nc("@info:status",
0579            "Update of <application>Hg</application> working directory is successful.");
0580     Q_EMIT infoMessage(xi18nc("@info:status",
0581            "Updating <application>Hg</application> working directory."));
0582 
0583     HgUpdateDialog dialog(m_parentWidget);
0584     dialog.exec();
0585 }
0586 
0587 void FileViewHgPlugin::branch()
0588 {
0589     m_errorMsg = xi18nc("@info:status",
0590            "Branch operation on <application>Hg</application> repository failed.");
0591     m_operationCompletedMsg = xi18nc("@info:status",
0592            "Branch operation on <application>Hg</application> repository completed successfully.");
0593     Q_EMIT infoMessage(xi18nc("@info:status",
0594            "Branch operation on <application>Hg</application> repository."));
0595 
0596     HgBranchDialog dialog(m_parentWidget);
0597     dialog.exec();
0598 }
0599 
0600 void FileViewHgPlugin::clone()
0601 {
0602     clearMessages();
0603     HgCloneDialog dialog(m_universalCurrentDirectory, m_parentWidget);
0604     dialog.exec();
0605 }
0606 
0607 void FileViewHgPlugin::create()
0608 {
0609     clearMessages();
0610     HgCreateDialog dialog(m_universalCurrentDirectory, m_parentWidget);
0611     dialog.exec();
0612 }
0613 
0614 void FileViewHgPlugin::global_config()
0615 {
0616     clearMessages();
0617     HgConfigDialog diag(HgConfig::GlobalConfig, m_parentWidget);
0618     diag.exec();
0619 }
0620 
0621 void FileViewHgPlugin::repo_config()
0622 {
0623     clearMessages();
0624     HgConfigDialog diag(HgConfig::RepoConfig, m_parentWidget);
0625     diag.exec();
0626 }
0627 
0628 void FileViewHgPlugin::push()
0629 {
0630     clearMessages();
0631     HgPushDialog diag(m_parentWidget);
0632     diag.exec();
0633 }
0634 
0635 void FileViewHgPlugin::pull()
0636 {
0637     clearMessages();
0638     HgPullDialog diag(m_parentWidget);
0639     diag.exec();
0640 }
0641 
0642 void FileViewHgPlugin::merge()
0643 {
0644     clearMessages();
0645     HgMergeDialog diag(m_parentWidget);
0646     diag.exec();
0647 }
0648 
0649 void FileViewHgPlugin::bundle()
0650 {
0651     clearMessages();
0652     HgBundleDialog diag(m_parentWidget);
0653     diag.exec();
0654 }
0655 
0656 void FileViewHgPlugin::unbundle()
0657 {
0658     clearMessages();
0659     QString bundle = QFileDialog::getOpenFileName();
0660     if (bundle.isEmpty()) {
0661         return;
0662     }
0663 
0664     QStringList args;
0665     args << bundle;
0666     if (m_hgWrapper->executeCommandTillFinished(QLatin1String("unbundle"), args)) {
0667     }
0668     else {
0669         KMessageBox::error(nullptr, m_hgWrapper->readAllStandardError());
0670     }
0671 }
0672 
0673 void FileViewHgPlugin::importChangesets()
0674 {
0675     clearMessages();
0676     HgImportDialog diag(m_parentWidget);
0677     diag.exec();
0678 }
0679 
0680 void FileViewHgPlugin::exportChangesets()
0681 {
0682     clearMessages();
0683     HgExportDialog diag(m_parentWidget);
0684     diag.exec();
0685 }
0686 
0687 void FileViewHgPlugin::revert()
0688 {
0689     clearMessages();
0690     int answer = KMessageBox::questionTwoActions(nullptr,
0691                                             xi18nc("@message:yesorno",
0692                     "Would you like to revert changes "
0693                     "made to selected files?"), i18n("Revert"), KGuiItem(i18n("Revert")), KStandardGuiItem::cancel());
0694     if (answer == KMessageBox::ButtonCode::SecondaryAction) {
0695         return;
0696     }
0697 
0698     QString infoMsg = xi18nc("@info:status",
0699          "Reverting files in <application>Hg</application> repository...");
0700     m_errorMsg = xi18nc("@info:status",
0701          "Reverting files in <application>Hg</application> repository failed.");
0702     m_operationCompletedMsg = xi18nc("@info:status",
0703          "Reverting files in <application>Hg</application> repository completed successfully.");
0704 
0705     Q_EMIT infoMessage(infoMsg);
0706     m_hgWrapper->revert(m_contextItems);
0707 }
0708 
0709 void FileViewHgPlugin::revertAll()
0710 {
0711     int answer = KMessageBox::questionTwoActions(nullptr,
0712                                             xi18nc("@message:yesorno",
0713                     "Would you like to revert all changes "
0714                     "made to current working directory?"), i18n("Revert All"), KGuiItem(i18n("Revert")), KStandardGuiItem::cancel());
0715     if (answer == KMessageBox::ButtonCode::SecondaryAction) {
0716         return;
0717     }
0718 
0719     QString infoMsg = xi18nc("@info:status",
0720          "Reverting files in <application>Hg</application> repository...");
0721     m_errorMsg = xi18nc("@info:status",
0722          "Reverting files in <application>Hg</application> repository failed.");
0723     m_operationCompletedMsg = xi18nc("@info:status",
0724          "Reverting files in <application>Hg</application> repository completed successfully.");
0725 
0726     Q_EMIT infoMessage(infoMsg);
0727     m_hgWrapper->revertAll();
0728 }
0729 
0730 void FileViewHgPlugin::diff()
0731 {
0732     QString infoMsg = xi18nc("@info:status",
0733          "Generating diff for <application>Hg</application> repository...");
0734     m_errorMsg = xi18nc("@info:status",
0735          "Could not get <application>Hg</application> repository diff.");
0736     m_operationCompletedMsg = xi18nc("@info:status",
0737          "Generated <application>Hg</application> diff successfully.");
0738 
0739     Q_EMIT infoMessage(infoMsg);
0740 
0741     QStringList args;
0742     args << QLatin1String("--config");
0743     args << QLatin1String("extensions.hgext.extdiff=");
0744     args << QLatin1String("-p");
0745     args << this->visualDiffExecPath();
0746 
0747     if (m_contextItems.length() == 1) {
0748         args << m_contextItems.takeFirst().localPath();
0749     }
0750 
0751     m_hgWrapper->executeCommand(QLatin1String("extdiff"), args);
0752 }
0753 
0754 void FileViewHgPlugin::serve()
0755 {
0756     clearMessages();
0757     HgServeDialog diag(m_parentWidget);
0758     diag.exec();
0759 }
0760 
0761 void FileViewHgPlugin::backout()
0762 {
0763     clearMessages();
0764     m_hgWrapper = HgWrapper::instance();
0765     if (!m_hgWrapper->isWorkingDirectoryClean()) {
0766         KMessageBox::error(nullptr, xi18nc("@message:error",
0767                       "abort: Uncommitted changes in working directory!"));
0768         return;
0769     }
0770 
0771     HgBackoutDialog diag(m_parentWidget);
0772     diag.exec();
0773 }
0774 
0775 void FileViewHgPlugin::rollback()
0776 {
0777     // execute a dry run rollback first to see if there is anything to
0778     // be rolled back, or check what will be rolled back
0779     if (!m_hgWrapper->rollback(true)) {
0780         KMessageBox::error(nullptr, xi18nc("@info:message", "No rollback "
0781                                         "information available!"));
0782         return;
0783     }
0784     // get what will be rolled back
0785     QString lastTransaction = m_hgWrapper->readAllStandardOutput();
0786     int cutOfFrom = lastTransaction.indexOf(QRegularExpression(QStringLiteral("\\d")));
0787     lastTransaction = lastTransaction.mid(cutOfFrom);
0788 
0789     // ask
0790     int answer = KMessageBox::questionTwoActions(nullptr,
0791                                             xi18nc("@message:yesorno",
0792                     "Would you like to rollback last transaction?")
0793                         + QLatin1String("\nrevision: ") + lastTransaction, i18n("Rollback"), KGuiItem(i18n("Rollback")), KStandardGuiItem::cancel());
0794     if (answer == KMessageBox::ButtonCode::SecondaryAction) {
0795         return;
0796     }
0797 
0798     QString infoMsg = xi18nc("@info:status",
0799          "Executing Rollback <application>Hg</application> repository...");
0800     m_errorMsg = xi18nc("@info:status",
0801          "Rollback of <application>Hg</application> repository failed.");
0802     m_operationCompletedMsg = xi18nc("@info:status",
0803          "Rollback of <application>Hg</application> repository completed successfully.");
0804 
0805     Q_EMIT infoMessage(infoMsg);
0806     m_hgWrapper->rollback();
0807     KMessageBox::information(nullptr, m_hgWrapper->readAllStandardOutput());
0808     Q_EMIT itemVersionsChanged();
0809 }
0810 
0811 void FileViewHgPlugin::slotOperationCompleted(int exitCode, QProcess::ExitStatus exitStatus)
0812 {
0813     if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) {
0814         Q_EMIT errorMessage(m_errorMsg);
0815     }
0816     else {
0817         m_contextItems.clear();
0818         Q_EMIT operationCompletedMessage(m_operationCompletedMsg);
0819         Q_EMIT itemVersionsChanged();
0820     }
0821 }
0822 
0823 void FileViewHgPlugin::slotOperationError()
0824 {
0825     m_contextItems.clear();
0826     Q_EMIT errorMessage(m_errorMsg);
0827 }
0828 
0829 void FileViewHgPlugin::clearMessages() const
0830 {
0831     m_operationCompletedMsg.clear();
0832     m_errorMsg.clear();
0833 }
0834 
0835 QString FileViewHgPlugin::visualDiffExecPath()
0836 {
0837     KConfig config(QStringLiteral("dolphin-hg"), KConfig::SimpleConfig,
0838                            QStandardPaths::GenericConfigLocation);
0839 
0840     KConfigGroup group(&config, QStringLiteral("diff"));
0841     QString result = group.readEntry(QLatin1String("exec"), QString()).trimmed();
0842 
0843     if (result.length() > 0) {
0844         return result;
0845     }
0846 
0847     KService::Ptr service = KApplicationTrader::preferredService(QStringLiteral("text/x-diff"));
0848     return service ? service->exec().split(QLatin1Char(' ')).takeFirst() : QString();
0849 }
0850 
0851 #include "fileviewhgplugin.moc"
0852 
0853 #include "moc_fileviewhgplugin.cpp"