File indexing completed on 2024-05-19 04:03:03

0001 /*
0002     SPDX-FileCopyrightText: 2017 René J.V. Bertin <rjvbertin@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "phabricatorjobs.h"
0008 #include "debug.h"
0009 
0010 #include <QDir>
0011 #include <QRegularExpression>
0012 #include <QStandardPaths>
0013 
0014 #include <KLocalizedString>
0015 
0016 const char s_colourCodes[] = "\u001B[[0-9]*m";
0017 
0018 using namespace Phabricator;
0019 
0020 bool DifferentialRevision::buildArcCommand(const QString &workDir, const QString &patchFile, bool doBrowse)
0021 {
0022     bool ret;
0023     QString arc = QStandardPaths::findExecutable(QStringLiteral("arc"));
0024     if (!arc.isEmpty()) {
0025         QStringList args;
0026         args << QStringLiteral("diff");
0027         if (m_id.isEmpty()) {
0028             // creating a new differential revision (review request)
0029             // the fact we skip "--create" means we'll be creating a new "differential diff"
0030             // which obliges the user to fill in the details we cannot provide through the plugin ATM.
0031             // TODO: grab the TARGET_GROUPS from .reviewboardrc and pass that via --reviewers
0032         } else {
0033             // updating an existing differential revision (review request)
0034             args << QStringLiteral("--update") << m_id;
0035         }
0036         args << QStringLiteral("--excuse") << QStringLiteral("patch submitted with the purpose/phabricator plugin");
0037         if (m_commit.isEmpty()) {
0038             args << QStringLiteral("--raw");
0039         } else {
0040             args << QStringLiteral("--allow-untracked") << QStringLiteral("--ignore-unsound-tests") << QStringLiteral("--nolint") << QStringLiteral("-nounit")
0041                  << QStringLiteral("--verbatim") << m_commit;
0042         }
0043         if (doBrowse) {
0044             args << QStringLiteral("--browse");
0045         }
0046         m_arcCmd.setProgram(arc);
0047         m_arcCmd.setArguments(args);
0048         if (!patchFile.isEmpty()) {
0049             m_arcCmd.setStandardInputFile(patchFile);
0050             m_arcInput = patchFile;
0051         }
0052         m_arcCmd.setWorkingDirectory(workDir);
0053         connect(&m_arcCmd, &QProcess::finished, this, &DifferentialRevision::done);
0054         setPercent(33);
0055         ret = true;
0056     } else {
0057         qCWarning(PLUGIN_PHABRICATOR) << "Could not find 'arc' in the path";
0058         setError(KJob::UserDefinedError + 3);
0059         setErrorText(i18n("Could not find the 'arc' command"));
0060         setErrorString(errorText());
0061         ret = false;
0062     }
0063     return ret;
0064 }
0065 
0066 void DifferentialRevision::start()
0067 {
0068     if (!m_arcCmd.program().isEmpty()) {
0069         qCDebug(PLUGIN_PHABRICATOR) << "starting" << m_arcCmd.program() << m_arcCmd.arguments();
0070         qCDebug(PLUGIN_PHABRICATOR) << "\twordDir=" << m_arcCmd.workingDirectory() << "stdin=" << m_arcInput;
0071         m_arcCmd.start();
0072         if (m_arcCmd.waitForStarted(5000)) {
0073             setPercent(66);
0074         }
0075     }
0076 }
0077 
0078 void DifferentialRevision::setErrorString(const QString &msg)
0079 {
0080     QRegularExpression unwanted(QString::fromUtf8(s_colourCodes));
0081     m_errorString = msg;
0082     m_errorString.replace(unwanted, QString());
0083 }
0084 
0085 QString DifferentialRevision::scrubbedResult()
0086 {
0087     QString result = QString::fromUtf8(m_arcCmd.readAllStandardOutput());
0088     // the return string can contain terminal text colour codes: remove them.
0089     QRegularExpression unwanted(QString::fromUtf8(s_colourCodes));
0090     result.replace(unwanted, QString());
0091     return result;
0092 }
0093 
0094 QStringList DifferentialRevision::scrubbedResultList()
0095 {
0096     QStringList result = QString::fromUtf8(m_arcCmd.readAllStandardOutput()).split(QChar::LineFeed);
0097     // the return string can contain terminal text colour codes: remove them.
0098     QRegularExpression unwanted(QString::fromUtf8(s_colourCodes));
0099     result.replaceInStrings(unwanted, QString());
0100     // remove all (now) empty strings
0101     result.removeAll(QString());
0102     return result;
0103 }
0104 
0105 NewDiffRev::NewDiffRev(const QUrl &patch, const QString &projectPath, bool doBrowse, QObject *parent)
0106     : DifferentialRevision(QString(), parent)
0107     , m_patch(patch)
0108     , m_project(projectPath)
0109 {
0110     buildArcCommand(projectPath, patch.toLocalFile(), doBrowse);
0111 }
0112 
0113 void NewDiffRev::done(int exitCode, QProcess::ExitStatus exitStatus)
0114 {
0115     if (exitStatus != QProcess::NormalExit || exitCode) {
0116         setError(KJob::UserDefinedError + exitCode);
0117         setErrorText(i18n("Could not create the new \"differential diff\""));
0118         setErrorString(QString::fromUtf8(m_arcCmd.readAllStandardError()));
0119         qCWarning(PLUGIN_PHABRICATOR) << "Could not create the new \"differential diff\":" << m_arcCmd.error() << ";" << errorString();
0120     } else {
0121         setPercent(99);
0122         const QString arcOutput = scrubbedResult();
0123         const char *diffOpCode = "Diff URI: ";
0124         int diffOffset = arcOutput.indexOf(QLatin1String(diffOpCode));
0125         if (diffOffset >= 0) {
0126             m_diffURI = arcOutput.mid(diffOffset + strlen(diffOpCode)).split(QChar::LineFeed).at(0);
0127         } else {
0128             m_diffURI = arcOutput;
0129         }
0130     }
0131 
0132     emitResult();
0133 }
0134 
0135 UpdateDiffRev::UpdateDiffRev(const QUrl &patch, const QString &basedir, const QString &id, const QString &updateComment, bool doBrowse, QObject *parent)
0136     : DifferentialRevision(id, parent)
0137     , m_patch(patch)
0138     , m_basedir(basedir)
0139 {
0140     buildArcCommand(m_basedir, m_patch.toLocalFile(), doBrowse);
0141     QStringList args = m_arcCmd.arguments();
0142     if (updateComment.isEmpty()) {
0143         args << QStringLiteral("--message") << QStringLiteral("<placeholder: patch updated via the purpose/phabricator plugin>");
0144     } else {
0145         args << QStringLiteral("--message") << updateComment;
0146     }
0147     m_arcCmd.setArguments(args);
0148 }
0149 
0150 void UpdateDiffRev::done(int exitCode, QProcess::ExitStatus exitStatus)
0151 {
0152     if (exitStatus != QProcess::NormalExit || exitCode) {
0153         setError(KJob::UserDefinedError + exitCode);
0154         setErrorText(i18n("Patch upload to Phabricator failed"));
0155         setErrorString(QString::fromUtf8(m_arcCmd.readAllStandardError()));
0156         qCWarning(PLUGIN_PHABRICATOR) << "Patch upload to Phabricator failed with exit code" << exitCode << ", error" << m_arcCmd.error() << ";"
0157                                       << errorString();
0158     } else {
0159         const QString arcOutput = scrubbedResult();
0160         const char *diffOpCode = "Revision URI: ";
0161         int diffOffset = arcOutput.indexOf(QLatin1String(diffOpCode));
0162         if (diffOffset >= 0) {
0163             m_diffURI = arcOutput.mid(diffOffset + strlen(diffOpCode)).split(QChar::LineFeed).at(0);
0164         } else {
0165             m_diffURI = arcOutput;
0166         }
0167     }
0168     emitResult();
0169 }
0170 
0171 DiffRevList::DiffRevList(const QString &projectDir, QObject *parent)
0172     : DifferentialRevision(QString(), parent)
0173     , m_projectDir(projectDir)
0174 {
0175     buildArcCommand(m_projectDir);
0176 }
0177 
0178 bool Phabricator::DiffRevList::buildArcCommand(const QString &workDir, const QString &unused, bool)
0179 {
0180     Q_UNUSED(unused)
0181     bool ret;
0182     QString arc = QStandardPaths::findExecutable(QStringLiteral("arc"));
0183     if (!arc.isEmpty()) {
0184         QStringList args;
0185         args << QStringLiteral("list");
0186         m_arcCmd.setProgram(arc);
0187         m_arcCmd.setArguments(args);
0188         m_arcCmd.setWorkingDirectory(workDir);
0189         connect(&m_arcCmd, &QProcess::finished, this, &DiffRevList::done);
0190         setPercent(33);
0191         ret = true;
0192     } else {
0193         qCWarning(PLUGIN_PHABRICATOR) << "Could not find 'arc' in the path";
0194         setError(KJob::UserDefinedError + 3);
0195         setErrorText(i18n("Could not find the 'arc' command"));
0196         setErrorString(errorText());
0197         ret = false;
0198     }
0199     return ret;
0200 }
0201 
0202 void DiffRevList::done(int exitCode, QProcess::ExitStatus exitStatus)
0203 {
0204     if (exitStatus != QProcess::NormalExit || exitCode) {
0205         setError(KJob::UserDefinedError + exitCode);
0206         setErrorText(i18n("Could not get list of differential revisions in %1", QDir::currentPath()));
0207         setErrorString(QString::fromUtf8(m_arcCmd.readAllStandardError()));
0208         qCWarning(PLUGIN_PHABRICATOR) << "Could not get list of differential revisions" << m_arcCmd.error() << ";" << errorString();
0209     } else {
0210         setPercent(99);
0211         const QStringList reviews = scrubbedResultList();
0212         qCDebug(PLUGIN_PHABRICATOR) << "arc list returned:" << reviews;
0213         const QRegularExpression revIDExpr(QStringLiteral(" D[0-9][0-9]*: "));
0214         for (auto rev : reviews) {
0215             int idStart = rev.indexOf(revIDExpr);
0216             if (idStart >= 0) {
0217                 QString revID = rev.mid(idStart + 1).split(QString::fromUtf8(": ")).at(0);
0218                 QString revTitle = rev.section(revIDExpr, 1);
0219                 if (rev.startsWith(QStringLiteral("* Accepted "))) {
0220                     // append a Unicode "Heavy Check Mark" to signal accepted revisions
0221                     revTitle += QStringLiteral(" ") + QString(QChar(0x2714));
0222                     m_statusMap[revTitle] = Accepted;
0223                 } else if (rev.startsWith(QStringLiteral("* Needs Revision "))) {
0224                     // append a Unicode "Heavy Ballot X" for lack of a Unicode glyph
0225                     // resembling the icon used on the Phab site.
0226                     revTitle += QStringLiteral(" ") + QString(QChar(0x2718));
0227                     m_statusMap[revTitle] = NeedsRevision;
0228                 } else if (rev.startsWith(QStringLiteral("* Needs Review "))) {
0229                     m_statusMap[revTitle] = NeedsReview;
0230                 }
0231                 m_reviews << qMakePair(revID, revTitle);
0232                 m_revMap[revTitle] = revID;
0233             }
0234         }
0235     }
0236     emitResult();
0237 }
0238 
0239 #include "moc_phabricatorjobs.cpp"