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"