File indexing completed on 2024-05-12 05:51:44

0001 /*
0002     SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "gitutils.h"
0008 
0009 #include "hostprocess.h"
0010 #include <gitprocess.h>
0011 
0012 #include <QProcess>
0013 
0014 bool GitUtils::isGitRepo(const QString &repo)
0015 {
0016     QProcess git;
0017     if (!setupGitProcess(git, repo, {QStringLiteral("rev-parse"), QStringLiteral("--is-inside-work-tree")})) {
0018         return false;
0019     }
0020 
0021     startHostProcess(git, QProcess::ReadOnly);
0022     if (git.waitForStarted() && git.waitForFinished(-1)) {
0023         return git.readAll().trimmed() == "true";
0024     }
0025     return false;
0026 }
0027 
0028 QString GitUtils::getCurrentBranchName(const QString &repo)
0029 {
0030     // clang-format off
0031     QStringList argsList[3] =
0032     {
0033         {QStringLiteral("symbolic-ref"), QStringLiteral("--short"), QStringLiteral("HEAD")},
0034         {QStringLiteral("describe"), QStringLiteral("--exact-match"), QStringLiteral("HEAD")},
0035         {QStringLiteral("rev-parse"), QStringLiteral("--short"), QStringLiteral("HEAD")}
0036     };
0037     // clang-format on
0038 
0039     for (int i = 0; i < 3; ++i) {
0040         QProcess git;
0041         if (!setupGitProcess(git, repo, argsList[i])) {
0042             return QString();
0043         }
0044 
0045         startHostProcess(git, QProcess::ReadOnly);
0046         if (git.waitForStarted() && git.waitForFinished(-1)) {
0047             if (git.exitStatus() == QProcess::NormalExit && git.exitCode() == 0) {
0048                 return QString::fromUtf8(git.readAllStandardOutput().trimmed());
0049             }
0050         }
0051     }
0052 
0053     // give up
0054     return QString();
0055 }
0056 
0057 GitUtils::CheckoutResult GitUtils::checkoutBranch(const QString &repo, const QString &branch)
0058 {
0059     QProcess git;
0060     if (!setupGitProcess(git, repo, {QStringLiteral("checkout"), branch})) {
0061         return CheckoutResult{};
0062     }
0063 
0064     startHostProcess(git, QProcess::ReadOnly);
0065     CheckoutResult res;
0066     res.branch = branch;
0067     if (git.waitForStarted() && git.waitForFinished(-1)) {
0068         res.returnCode = git.exitCode();
0069         res.error = QString::fromUtf8(git.readAllStandardError());
0070     }
0071     return res;
0072 }
0073 
0074 GitUtils::CheckoutResult GitUtils::checkoutNewBranch(const QString &repo, const QString &newBranch, const QString &fromBranch)
0075 {
0076     QProcess git;
0077     QStringList args{QStringLiteral("checkout"), QStringLiteral("-q"), QStringLiteral("-b"), newBranch};
0078     if (!fromBranch.isEmpty()) {
0079         args.append(fromBranch);
0080     }
0081 
0082     if (!setupGitProcess(git, repo, args)) {
0083         return CheckoutResult{};
0084     }
0085 
0086     startHostProcess(git, QProcess::ReadOnly);
0087     CheckoutResult res;
0088     res.branch = newBranch;
0089     if (git.waitForStarted() && git.waitForFinished(-1)) {
0090         res.returnCode = git.exitCode();
0091         res.error = QString::fromUtf8(git.readAllStandardError());
0092     }
0093     return res;
0094 }
0095 
0096 static GitUtils::Branch parseLocalBranch(const QString &raw)
0097 {
0098     static const int len = QStringLiteral("refs/heads/").length();
0099     return GitUtils::Branch{raw.mid(len), QString(), GitUtils::Head, QString()};
0100 }
0101 
0102 static GitUtils::Branch parseRemoteBranch(const QString &raw)
0103 {
0104     static const int len = QStringLiteral("refs/remotes/").length();
0105     int indexofRemote = raw.indexOf(QLatin1Char('/'), len);
0106     return GitUtils::Branch{raw.mid(len), raw.mid(len, indexofRemote - len), GitUtils::Remote, QString()};
0107 }
0108 
0109 QList<GitUtils::Branch> GitUtils::getAllBranchesAndTags(const QString &repo, RefType ref)
0110 {
0111     // git for-each-ref --format '%(refname)' --sort=-committerdate ...
0112     QProcess git;
0113 
0114     QStringList args{QStringLiteral("for-each-ref"), QStringLiteral("--format"), QStringLiteral("%(refname)"), QStringLiteral("--sort=-committerdate")};
0115     if (ref & RefType::Head) {
0116         args.append(QStringLiteral("refs/heads"));
0117     }
0118     if (ref & RefType::Remote) {
0119         args.append(QStringLiteral("refs/remotes"));
0120     }
0121     if (ref & RefType::Tag) {
0122         args.append(QStringLiteral("refs/tags"));
0123         args.append(QStringLiteral("--sort=-taggerdate"));
0124     }
0125 
0126     if (!setupGitProcess(git, repo, args)) {
0127         return {};
0128     }
0129 
0130     startHostProcess(git, QProcess::ReadOnly);
0131     QList<Branch> branches;
0132     if (git.waitForStarted() && git.waitForFinished(-1)) {
0133         QString gitout = QString::fromUtf8(git.readAllStandardOutput());
0134         QStringList out = gitout.split(QLatin1Char('\n'));
0135 
0136         branches.reserve(out.size());
0137         // clang-format off
0138         for (const auto &o : out) {
0139             if (ref & Head && o.startsWith(QLatin1String("refs/heads"))) {
0140                 branches.append(parseLocalBranch(o));
0141             } else if (ref & Remote && o.startsWith(QLatin1String("refs/remotes"))) {
0142                 branches.append(parseRemoteBranch(o));
0143             } else if (ref & Tag && o.startsWith(QLatin1String("refs/tags/"))) {
0144                 static const int len = QStringLiteral("refs/tags/").length();
0145                 branches.append({o.mid(len), {}, RefType::Tag, QString()});
0146             }
0147         }
0148         // clang-format on
0149     }
0150 
0151     return branches;
0152 }
0153 
0154 QList<GitUtils::Branch> GitUtils::getAllLocalBranchesWithLastCommitSubject(const QString &repo)
0155 {
0156     // git for-each-ref --format '%(refname)' --sort=-committerdate ...
0157     QProcess git;
0158 
0159     QStringList args{QStringLiteral("for-each-ref"),
0160                      QStringLiteral("--format"),
0161                      QStringLiteral("%(refname)[--]%(contents:subject)"),
0162                      QStringLiteral("--sort=-committerdate"),
0163                      QStringLiteral("refs/heads")};
0164 
0165     if (!setupGitProcess(git, repo, args)) {
0166         return {};
0167     }
0168 
0169     startHostProcess(git, QProcess::ReadOnly);
0170     QList<Branch> branches;
0171     if (git.waitForStarted() && git.waitForFinished(-1)) {
0172         QByteArray gitout = git.readAllStandardOutput();
0173         QByteArrayList rows = gitout.split('\n');
0174 
0175         branches.reserve(rows.size());
0176         constexpr int len = sizeof("refs/heads/") - 1;
0177         for (const auto &row : rows) {
0178             int seperatorIdx = row.indexOf("[--]", len);
0179             if (seperatorIdx == -1) {
0180                 continue;
0181             }
0182             int commitStart = seperatorIdx + 4;
0183             branches << GitUtils::Branch{QString::fromUtf8(row.mid(len, seperatorIdx - len)),
0184                                          QString(),
0185                                          GitUtils::Head,
0186                                          QString::fromUtf8(row.mid(commitStart))};
0187         }
0188     }
0189 
0190     return branches;
0191 }
0192 
0193 QList<GitUtils::Branch> GitUtils::getAllBranches(const QString &repo)
0194 {
0195     return getAllBranchesAndTags(repo, static_cast<RefType>(RefType::Head | RefType::Remote));
0196 }
0197 
0198 std::pair<QString, QString> GitUtils::getLastCommitMessage(const QString &repo)
0199 {
0200     // git log -1 --pretty=%B
0201     QProcess git;
0202     if (!setupGitProcess(git, repo, {QStringLiteral("log"), QStringLiteral("-1"), QStringLiteral("--pretty=%B")})) {
0203         return {};
0204     }
0205 
0206     startHostProcess(git, QProcess::ReadOnly);
0207     if (git.waitForStarted() && git.waitForFinished(-1)) {
0208         if (git.exitCode() != 0 || git.exitStatus() != QProcess::NormalExit) {
0209             return {};
0210         }
0211 
0212         QList<QByteArray> output = git.readAllStandardOutput().split('\n');
0213         if (output.isEmpty()) {
0214             return {};
0215         }
0216 
0217         QString msg = QString::fromUtf8(output.at(0));
0218         QString desc;
0219         if (output.size() > 1) {
0220             desc = std::accumulate(output.cbegin() + 1, output.cend(), QString::fromUtf8(output.at(1)), [](const QString &line, const QByteArray &ba) {
0221                 return QString(line + QString::fromUtf8(ba) + QStringLiteral("\n"));
0222             });
0223             desc = desc.trimmed();
0224         }
0225         return {msg, desc};
0226     }
0227     return {};
0228 }
0229 
0230 GitUtils::Result GitUtils::deleteBranches(const QStringList &branches, const QString &repo)
0231 {
0232     QStringList args = {QStringLiteral("branch"), QStringLiteral("-D")};
0233     args << branches;
0234 
0235     QProcess git;
0236     if (!setupGitProcess(git, repo, args)) {
0237         return {};
0238     }
0239 
0240     startHostProcess(git, QProcess::ReadOnly);
0241     if (git.waitForStarted() && git.waitForFinished(-1)) {
0242         QString out = QString::fromLatin1(git.readAllStandardError()) + QString::fromLatin1(git.readAllStandardOutput());
0243         return {out, git.exitCode()};
0244     }
0245     Q_UNREACHABLE();
0246     return {QString(), -1};
0247 }