File indexing completed on 2024-04-28 05:49:27

0001 /*
0002     SPDX-FileCopyrightText: 2010 Christoph Cullmann <cullmann@kde.org>
0003 
0004     SPDX-License-Identifier: MIT
0005 */
0006 
0007 #include "gitprocess.h"
0008 #include "hostprocess.h"
0009 
0010 #include <QProcess>
0011 #include <QRegularExpression>
0012 #include <QStandardPaths>
0013 
0014 #include <QIcon>
0015 
0016 #include <KIconLoader>
0017 
0018 bool setupGitProcess(QProcess &process, const QString &workingDirectory, const QStringList &arguments)
0019 {
0020     // only use git from PATH
0021     static const auto gitExecutable = safeExecutableName(QStringLiteral("git"));
0022     if (gitExecutable.isEmpty()) {
0023         // ensure we have no valid QProcess setup
0024         process.setProgram(QString());
0025         return false;
0026     }
0027 
0028     // setup program and arguments, ensure we do run git in the right working directory
0029     process.setProgram(gitExecutable);
0030     process.setWorkingDirectory(workingDirectory);
0031     process.setArguments(arguments);
0032 
0033     /**
0034      * from the git manual:
0035      *
0036      * If set to 0, Git will complete any requested operation without performing any optional sub-operations that require taking a lock.
0037      * For example, this will prevent git status from refreshing the index as a side effect.
0038      * This is useful for processes running in the background which do not want to cause lock contention with other operations on the repository.
0039      * Defaults to 1.
0040      *
0041      * we use the env var as this is compatible even for "ancient" git versions pre 2.15.2
0042      */
0043     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
0044     env.insert(QStringLiteral("GIT_OPTIONAL_LOCKS"), QStringLiteral("0"));
0045     process.setProcessEnvironment(env);
0046     return true;
0047 }
0048 
0049 // internal helper for the external caching accessor
0050 static std::pair<int, int> getGitVersionUncached(const QString &workingDir)
0051 {
0052     QProcess git;
0053     if (!setupGitProcess(git, workingDir, {QStringLiteral("--version")})) {
0054         return {-1, -1};
0055     }
0056 
0057     // try to run, no version output feasible if not possible
0058     startHostProcess(git, QProcess::ReadOnly);
0059     if (!git.waitForStarted() || !git.waitForFinished() || git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
0060         return {-1, -1};
0061     }
0062 
0063     // match the version output
0064     const QString gitVersion = QString::fromUtf8(git.readAllStandardOutput());
0065     const QRegularExpression gitRegex(QStringLiteral("git version\\s*(\\d+).(\\d+).(\\d+)+.*"));
0066     const QRegularExpressionMatch gitMatch = gitRegex.match(gitVersion);
0067 
0068     bool okMajor = false;
0069     bool okMinor = false;
0070     const int versionMajor = gitMatch.captured(1).toInt(&okMajor);
0071     const int versionMinor = gitMatch.captured(2).toInt(&okMinor);
0072     if (okMajor && okMinor) {
0073         return {versionMajor, versionMinor};
0074     }
0075 
0076     // no version properly detected
0077     return {-1, -1};
0078 }
0079 
0080 std::pair<int, int> getGitVersion(const QString &workingDir)
0081 {
0082     // cache internal result to avoid expensive recalculation
0083     static const auto cachedVersion = getGitVersionUncached(workingDir);
0084     return cachedVersion;
0085 }
0086 
0087 std::optional<QString> getRepoBasePath(const QString &repo)
0088 {
0089     /* This call is intentionally blocking because we need git path for everything else */
0090     QProcess git;
0091     if (!setupGitProcess(git, repo, {QStringLiteral("rev-parse"), QStringLiteral("--show-toplevel")})) {
0092         return std::nullopt;
0093     }
0094 
0095     startHostProcess(git, QProcess::ReadOnly);
0096     if (git.waitForStarted() && git.waitForFinished(-1)) {
0097         if (git.exitStatus() != QProcess::NormalExit || git.exitCode() != 0) {
0098             return std::nullopt;
0099         }
0100         QString dotGitPath = QString::fromUtf8(git.readAllStandardOutput().trimmed());
0101         if (!dotGitPath.endsWith(QLatin1Char('/'))) {
0102             dotGitPath.append(QLatin1Char('/'));
0103         }
0104         return dotGitPath;
0105     }
0106     return std::nullopt;
0107 }
0108 
0109 QIcon gitIcon()
0110 {
0111     static const auto icon = KDE::icon(QStringLiteral(":/icons/icons/sc-apps-git.svg"));
0112     return icon;
0113 }