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

0001 /*
0002     SPDX-FileCopyrightText: 2011 Vishesh Yadav <vishesh3y@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "hgwrapper.h"
0008 
0009 #include <QApplication>
0010 #include <QTextCodec>
0011 #include <QUrl>
0012 #include <QDebug>
0013 #include <QRegularExpression>
0014 
0015 //TODO: Replace start() with executeCommand functions wherever possible.
0016 //FIXME: Add/Remove/Revert argument length limit. Divide the list.
0017 //FIXME: Cannot create thread for parent that is in different thread.
0018 
0019 HgWrapper *HgWrapper::m_instance = nullptr;
0020 
0021 HgWrapper::HgWrapper(QObject *parent) :
0022     QObject(parent)
0023 {
0024     m_localCodec = QTextCodec::codecForLocale();
0025 
0026     // re-emit QProcess signals
0027     connect(&m_process, &QProcess::errorOccurred,
0028             this, &HgWrapper::errorOccurred);
0029     connect(&m_process, &QProcess::finished,
0030             this, &HgWrapper::finished);
0031     connect(&m_process, &QProcess::stateChanged,
0032             this, &HgWrapper::stateChanged);
0033     connect(&m_process, &QProcess::started,
0034             this, &HgWrapper::started);
0035 
0036     connect(&m_process, &QProcess::finished,
0037             this, &HgWrapper::slotOperationCompleted);
0038     connect(&m_process, &QProcess::errorOccurred,
0039             this, &HgWrapper::slotOperationError);
0040 
0041 }
0042 
0043 HgWrapper *HgWrapper::instance()
0044 {
0045     if (!m_instance) {
0046         m_instance = new HgWrapper;
0047     }
0048     return m_instance;
0049 }
0050 
0051 void HgWrapper::freeInstance()
0052 {
0053     delete m_instance;
0054     m_instance = nullptr;
0055 }
0056 
0057 void HgWrapper::slotOperationCompleted(int exitCode, 
0058                                        QProcess::ExitStatus exitStatus)
0059 {
0060     qDebug() << "'hg' Exit Code: " << exitCode << "  Exit Status: "
0061         << exitStatus;
0062     if (m_primaryOperation) {
0063         Q_EMIT primaryOperationFinished(exitCode, exitStatus);
0064     }
0065 }
0066 
0067 void HgWrapper::slotOperationError(QProcess::ProcessError error)
0068 {
0069     qDebug() << "Error occurred while executing 'hg' with arguments ";
0070     if (m_primaryOperation) {
0071         Q_EMIT primaryOperationError(error);
0072     }
0073 }
0074 
0075 bool HgWrapper::executeCommand(const QString &hgCommand,
0076                                const QStringList &arguments,
0077                                QString &output,
0078                                bool primaryOperation)
0079 {
0080     Q_ASSERT(m_process.state() == QProcess::NotRunning);
0081 
0082     executeCommand(hgCommand, arguments, primaryOperation);
0083     m_process.waitForFinished();
0084     output = QTextCodec::codecForLocale()->toUnicode(m_process.readAllStandardOutput());
0085 
0086     return (m_process.exitStatus() == QProcess::NormalExit &&
0087             m_process.exitCode() == 0);
0088 }
0089 
0090 void HgWrapper::executeCommand(const QString &hgCommand,
0091                                const QStringList &arguments,
0092                                bool primaryOperation)
0093 {
0094     Q_ASSERT(m_process.state() == QProcess::NotRunning);
0095 
0096     m_primaryOperation = primaryOperation;
0097     if (m_primaryOperation) {
0098         qDebug() << "Primary operation";
0099     }
0100 
0101     QStringList args;
0102     args << hgCommand;
0103     args << arguments;
0104     m_process.setWorkingDirectory(m_currentDir);
0105     m_process.start(QLatin1String("hg"), args);
0106 }
0107 
0108 bool HgWrapper::executeCommandTillFinished(const QString &hgCommand,
0109                                const QStringList &arguments,
0110                                bool primaryOperation)
0111 {
0112     Q_ASSERT(m_process.state() == QProcess::NotRunning);
0113 
0114     m_primaryOperation = primaryOperation;
0115 
0116     QStringList args;
0117     args << hgCommand;
0118     args << arguments;
0119     m_process.setWorkingDirectory(m_currentDir);
0120     m_process.start(QLatin1String("hg"), args);
0121     m_process.waitForFinished();
0122 
0123     return (m_process.exitStatus() == QProcess::NormalExit &&
0124             m_process.exitCode() == 0);
0125 }
0126 
0127 QString HgWrapper::getBaseDir() const
0128 {
0129     return m_hgBaseDir;
0130 }
0131 
0132 QString HgWrapper::getCurrentDir() const
0133 {
0134     return m_currentDir;
0135 }
0136 
0137 void HgWrapper::updateBaseDir()
0138 {
0139     m_process.setWorkingDirectory(m_currentDir);
0140     m_process.start(QStringLiteral("hg"), QStringList{QStringLiteral("root")});
0141     m_process.waitForFinished();
0142     m_hgBaseDir = QString::fromLocal8Bit(m_process.readAllStandardOutput()).trimmed();
0143 }
0144 
0145 void HgWrapper::setCurrentDir(const QString &directory)
0146 {
0147     m_currentDir = directory;
0148     updateBaseDir(); //now get root directory of repository
0149 }
0150 
0151 void  HgWrapper::setBaseAsWorkingDir()
0152 {
0153     m_process.setWorkingDirectory(getBaseDir());
0154 }
0155 
0156 void HgWrapper::addFiles(const KFileItemList &fileList)
0157 {
0158     Q_ASSERT(m_process.state() == QProcess::NotRunning);
0159 
0160     QStringList args;
0161     args << QStringLiteral("add");
0162     for (const KFileItem &item : fileList) {
0163         args << item.localPath();
0164     }
0165     m_process.start(QStringLiteral("hg"), args);
0166 }
0167 
0168 bool HgWrapper::renameFile(const QString &source, const QString &destination)
0169 {
0170     Q_ASSERT(m_process.state() == QProcess::NotRunning);
0171 
0172     QStringList args;
0173     args <<  source << destination;
0174     executeCommand(QStringLiteral("rename"), args, true);
0175 
0176     m_process.waitForFinished();
0177     return (m_process.exitStatus() == QProcess::NormalExit &&
0178             m_process.exitCode() == 0);
0179 }
0180 
0181 void HgWrapper::removeFiles(const KFileItemList &fileList)
0182 {
0183     Q_ASSERT(m_process.state() == QProcess::NotRunning);
0184 
0185     QStringList args{
0186         QStringLiteral("remove"),
0187         QStringLiteral("--force"),
0188     };
0189     for (const KFileItem &item : fileList) {
0190         args << item.localPath();
0191     }
0192     m_process.start(QStringLiteral("hg"), args);
0193 }
0194 
0195 bool HgWrapper::commit(const QString &message, const QStringList &files,
0196                        bool closeCurrentBranch)
0197 {
0198     QStringList args;
0199     args << files;
0200     args << QStringLiteral("-m") << message;
0201     if (closeCurrentBranch) {
0202         args << QStringLiteral("--close-branch");
0203     }
0204     executeCommand(QStringLiteral("commit"), args, true);
0205     m_process.waitForFinished();
0206     return (m_process.exitCode() == 0 &&
0207             m_process.exitStatus() == QProcess::NormalExit);
0208 }
0209 
0210 bool HgWrapper::createBranch(const QString &name)
0211 {
0212     QStringList args;
0213     args << name;
0214     executeCommand(QStringLiteral("branch"), args, true);
0215     m_process.waitForFinished();
0216     return (m_process.exitCode() == 0 &&
0217             m_process.exitStatus() == QProcess::NormalExit);
0218 }
0219 
0220 bool HgWrapper::switchBranch(const QString &name)
0221 {
0222     const QStringList args{
0223         QStringLiteral("-c"),
0224         name,
0225     };
0226     executeCommand(QStringLiteral("update"), args, true);
0227     m_process.waitForFinished();
0228     return (m_process.exitCode() == 0 &&
0229             m_process.exitStatus() == QProcess::NormalExit);
0230 }
0231 
0232 bool HgWrapper::createTag(const QString &name)
0233 {
0234     QStringList args;
0235     args << name;
0236     executeCommand(QStringLiteral("tag"), args, true);
0237     m_process.waitForFinished();
0238     return (m_process.exitCode() == 0 &&
0239             m_process.exitStatus() == QProcess::NormalExit);
0240 }
0241 
0242 bool HgWrapper::revertAll()
0243 {
0244     QStringList args;
0245     args << QStringLiteral("--all");
0246     return executeCommandTillFinished(QStringLiteral("revert"), args, true);
0247 }
0248 
0249 
0250 bool HgWrapper::revert(const KFileItemList &fileList)
0251 {
0252     QStringList arguments;
0253     for (const KFileItem &item : fileList) {
0254         arguments << item.localPath();
0255     }
0256     return executeCommandTillFinished(QStringLiteral("revert"), arguments, true);
0257 }
0258 
0259 bool HgWrapper::rollback(bool dryRun)
0260 {
0261     QStringList args;
0262     if (dryRun) {
0263         args << QStringLiteral("-n");
0264     }
0265     return executeCommandTillFinished(QStringLiteral("rollback"), args, true);
0266 }
0267 
0268 bool HgWrapper::switchTag(const QString &name)
0269 {
0270     QStringList args;
0271     args << QStringLiteral("-c") << name;
0272     executeCommand(QStringLiteral("update"), args, true);
0273     m_process.waitForFinished();
0274     return (m_process.exitCode() == 0 &&
0275             m_process.exitStatus() == QProcess::NormalExit);
0276 }
0277 
0278 //TODO: Make it return QStringList.
0279 QString HgWrapper::getParentsOfHead()
0280 {
0281     Q_ASSERT(m_process.state() == QProcess::NotRunning);
0282 
0283     QString output;
0284     const QStringList args{
0285         QStringLiteral("--template"),
0286         QStringLiteral("{rev}:{node|short}  "),
0287     };
0288     executeCommand(QStringLiteral("parents"), args, output);
0289     return output;
0290 }
0291 
0292 QStringList HgWrapper::getTags()
0293 {
0294     QStringList result;
0295     executeCommand(QStringLiteral("tags"));
0296     while (m_process.waitForReadyRead()) {
0297         char buffer[1048];
0298         while (m_process.readLine(buffer, sizeof(buffer)) > 0) {
0299             result << QString::fromLocal8Bit(buffer).split(QRegularExpression(QStringLiteral("\\s+")),
0300                                             Qt::SkipEmptyParts).first();
0301         }
0302     }
0303     return result;
0304 }
0305 
0306 QStringList HgWrapper::getBranches()
0307 {
0308     QStringList result;
0309     executeCommand(QStringLiteral("branches"));
0310     while (m_process.waitForReadyRead()) {
0311         char buffer[1048];
0312         while (m_process.readLine(buffer, sizeof(buffer)) > 0) {
0313             // 'hg branches' command lists the branches in following format
0314             // <branchname>      <revision:changeset_hash> [(inactive)]
0315             // Extract just the branchname
0316             result << QString::fromLocal8Bit(buffer).remove(QRegularExpression(QStringLiteral("[\\s]+[\\d:a-zA-Z\\(\\)]*")));
0317         }
0318     }
0319     return result;
0320 }
0321 
0322 void HgWrapper::getItemVersions(QHash<QString, KVersionControlPlugin::ItemVersion> &result)
0323 {
0324     /*int nTrimOutLeft = m_hgBaseDir.length();
0325     QString relativePrefix = m_currentDir.right(m_currentDir.length() -
0326                                                  nTrimOutLeft - 1);
0327     qDebug() << m_hgBaseDir << "     " << relativePrefix;*/
0328 
0329     // Get status of files
0330     const QStringList args{
0331         QStringLiteral("status"),
0332         QStringLiteral("--modified"),
0333         QStringLiteral("--added"),
0334         QStringLiteral("--removed"),
0335         QStringLiteral("--deleted"),
0336         QStringLiteral("--unknown"),
0337         QStringLiteral("--ignored"),
0338     };
0339     m_process.setWorkingDirectory(m_currentDir);
0340     m_process.start(QStringLiteral("hg"), args);
0341     while (m_process.waitForReadyRead()) {
0342         char buffer[1024];
0343         while (m_process.readLine(buffer, sizeof(buffer)) > 0)  {
0344             const QString currentLine(QTextCodec::codecForLocale()->toUnicode(buffer).trimmed());
0345             char currentStatus = buffer[0];
0346             QString currentFile = currentLine.mid(2);
0347             KVersionControlPlugin::ItemVersion vs = KVersionControlPlugin::NormalVersion;
0348             switch (currentStatus) {
0349                 case 'A':
0350                     vs = KVersionControlPlugin::AddedVersion;
0351                     break;
0352                 case 'M':
0353                     vs = KVersionControlPlugin::LocallyModifiedVersion;
0354                     break;
0355                 case '?':
0356                     vs = KVersionControlPlugin::UnversionedVersion;
0357                     break;
0358                 case 'R':
0359                     vs = KVersionControlPlugin::RemovedVersion;
0360                     break;
0361                 case 'I':
0362                     vs = KVersionControlPlugin::IgnoredVersion;
0363                     break;
0364                 case 'C':
0365                     vs = KVersionControlPlugin::NormalVersion;
0366                     break;
0367                 case '!':
0368                     vs = KVersionControlPlugin::MissingVersion;
0369                     break;
0370             }
0371             if (vs != KVersionControlPlugin::NormalVersion) {
0372                 // Get full path to file and insert it to result
0373                 QUrl url = QUrl::fromLocalFile(m_hgBaseDir);
0374                 url = url.adjusted(QUrl::StripTrailingSlash);
0375                 url.setPath(url.path() + QLatin1Char('/') + currentFile);
0376                 QString filePath = url.path();
0377                 result.insert(filePath, vs);
0378             }
0379         }
0380     }
0381 }
0382 
0383 void HgWrapper::terminateCurrentProcess()
0384 {
0385     qDebug() << "terminating";
0386     m_process.terminate();
0387 }
0388 
0389 bool HgWrapper::isWorkingDirectoryClean()
0390 {
0391     const QStringList args {
0392         QStringLiteral("--modified"),
0393         QStringLiteral("--added"),
0394         QStringLiteral("--removed"),
0395         QStringLiteral("--deleted"),
0396     };
0397 
0398     QString output;
0399     executeCommand(QStringLiteral("status"), args, output);
0400     
0401     return output.trimmed().isEmpty();
0402 }
0403 
0404 
0405 
0406 #include "moc_hgwrapper.cpp"