LCOV - code coverage report
Current view: top level - src/esys/repo/gitcmdline - git_cmdline.cpp (source / functions) Hit Total Coverage
Test: esysrepo_coverage.info Lines: 252 386 65.3 %
Date: 2026-08-01 10:43:40 Functions: 24 60 40.0 %

          Line data    Source code
       1             : /*!
       2             :  * \file esys/repo/gitcmdline/git_cmdline.cpp
       3             :  * \brief System ``git`` CLI backend (sync-capable: clone/fetch/checkout/…)
       4             :  *
       5             :  * \cond
       6             :  * __legal_b__
       7             :  *
       8             :  * Copyright (c) 2020-2026 Michel Gillet
       9             :  * Distributed under the MIT License.
      10             :  * (See accompanying file LICENSE.txt or
      11             :  * copy at https://opensource.org/licenses/MIT)
      12             :  *
      13             :  * __legal_e__
      14             :  * \endcond
      15             :  *
      16             :  */
      17             : 
      18             : #include "esys/repo/esysrepo_prec.h"
      19             : #include "esys/repo/gitcmdline/git.h"
      20             : 
      21             : #include "esys/repo/git/branch.h"
      22             : #include "esys/repo/git/commithash.h"
      23             : 
      24             : #include <boost/filesystem.hpp>
      25             : #include <boost/process/search_path.hpp>
      26             : 
      27             : #include <cstdio>
      28             : #include <cstdlib>
      29             : #include <sstream>
      30             : #include <string>
      31             : #include <vector>
      32             : 
      33             : #ifdef _WIN32
      34             : #define ESYSREPO_POPEN _popen
      35             : #define ESYSREPO_PCLOSE _pclose
      36             : #else
      37             : #define ESYSREPO_POPEN popen
      38             : #define ESYSREPO_PCLOSE pclose
      39             : #endif
      40             : 
      41             : namespace esys::repo::gitcmdline
      42             : {
      43             : 
      44             : namespace
      45             : {
      46             : 
      47         151 : std::string shell_quote(const std::string &arg)
      48             : {
      49             : #ifdef _WIN32
      50             :     // cmd.exe: double % (env expansion) and quote when spaces or shell metacharacters
      51             :     // appear (notably '|' in git for-each-ref --format).
      52             :     std::string escaped;
      53             :     escaped.reserve(arg.size() * 2);
      54             :     for (char c : arg)
      55             :     {
      56             :         if (c == '%') escaped += '%';
      57             :         escaped += c;
      58             :     }
      59             :     const bool need_quote = escaped.find_first_of(" \t\"|&<>^!") != std::string::npos;
      60             :     if (!need_quote) return escaped;
      61             :     std::string out = "\"";
      62             :     for (char c : escaped)
      63             :     {
      64             :         // cmd embedded quote is ""
      65             :         if (c == '"') out += '"';
      66             :         out += c;
      67             :     }
      68             :     out += '"';
      69             :     return out;
      70             : #else
      71         151 :     if (arg.find_first_of(" \t\"'\\") == std::string::npos) return arg;
      72           0 :     std::string out = "\"";
      73           0 :     for (char c : arg)
      74             :     {
      75           0 :         if (c == '"') out += '\\';
      76           0 :         out += c;
      77             :     }
      78           0 :     out += '"';
      79           0 :     return out;
      80             : #endif
      81         151 : }
      82             : 
      83          18 : std::string trim(std::string s)
      84             : {
      85          32 :     while (!s.empty() && (s.back() == '\n' || s.back() == '\r' || s.back() == ' ' || s.back() == '\t')) s.pop_back();
      86             :     std::size_t i = 0;
      87          18 :     while (i < s.size() && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r')) ++i;
      88          18 :     return s.substr(i);
      89             : }
      90             : 
      91           2 : std::string strip_rev_prefix(std::string rev)
      92             : {
      93           2 :     const std::string heads = "refs/heads/";
      94           2 :     const std::string tags = "refs/tags/";
      95           2 :     if (rev.compare(0, heads.size(), heads) == 0) return rev.substr(heads.size());
      96           2 :     if (rev.compare(0, tags.size(), tags) == 0) return rev.substr(tags.size());
      97           4 :     return rev;
      98           2 : }
      99             : 
     100           3 : std::vector<std::string> split_lines(const std::string &text)
     101             : {
     102           3 :     std::vector<std::string> lines;
     103           3 :     std::string cur;
     104         180 :     for (char c : text)
     105             :     {
     106         177 :         if (c == '\n')
     107             :         {
     108           3 :             if (!cur.empty() && cur.back() == '\r') cur.pop_back();
     109           3 :             lines.push_back(cur);
     110           3 :             cur.clear();
     111             :         }
     112             :         else
     113         351 :             cur += c;
     114             :     }
     115           3 :     if (!cur.empty())
     116             :     {
     117           0 :         if (cur.back() == '\r') cur.pop_back();
     118           0 :         lines.push_back(cur);
     119             :     }
     120           3 :     return lines;
     121           3 : }
     122             : 
     123             : } // namespace
     124             : 
     125             : bool Git::s_detect_ssh_agent_done = false;
     126             : bool Git::s_ssh_agent_running = false;
     127             : 
     128           0 : std::shared_ptr<GitBase> Git::new_ptr()
     129             : {
     130           0 :     return std::make_shared<Git>();
     131             : }
     132             : 
     133           6 : Git::Git()
     134           6 :     : GitBase()
     135             : {
     136           6 : }
     137             : 
     138           6 : Git::~Git()
     139             : {
     140           6 :     if (m_open) close();
     141           6 : }
     142             : 
     143          69 : std::string Git::find_git_executable()
     144             : {
     145          69 :     if (const char *env = std::getenv("ESYSREPO_GIT_EXE"))
     146             :     {
     147           0 :         if (env[0] != '\0' && boost::filesystem::exists(env)) return std::string(env);
     148             :     }
     149          69 :     if (const char *env = std::getenv("GIT_EXECUTABLE"))
     150             :     {
     151           0 :         if (env[0] != '\0' && boost::filesystem::exists(env)) return std::string(env);
     152             :     }
     153         138 :     boost::filesystem::path found = boost::process::search_path("git");
     154             :     // Prefer the bare name so std::system/cmd.exe does not break on spaces in
     155             :     // "C:\Program Files\Git\..." (PATH already contains the directory).
     156          69 :     if (!found.empty()) return "git";
     157          69 :     return {};
     158          69 : }
     159             : 
     160           0 : Result Git::not_implemented() const
     161             : {
     162           0 :     return ESYSREPO_RESULT(ResultCode::NOT_IMPLEMENTED);
     163             : }
     164             : 
     165          32 : std::string Git::build_git_command(const std::vector<std::string> &args, const std::string &cwd) const
     166             : {
     167          32 :     const std::string git = find_git_executable();
     168          32 :     std::ostringstream cmd;
     169          64 :     cmd << shell_quote(git);
     170          57 :     if (!cwd.empty()) cmd << " -C " << shell_quote(cwd);
     171         126 :     for (const auto &arg : args) cmd << ' ' << shell_quote(arg);
     172          32 :     return cmd.str();
     173          32 : }
     174             : 
     175           3 : Result Git::run_git(const std::vector<std::string> &args, const std::string &cwd)
     176             : {
     177           3 :     const std::string git = find_git_executable();
     178           3 :     if (git.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "git executable not found");
     179             : 
     180           3 :     const std::string cmd = build_git_command(args, cwd);
     181           6 :     debug(1, std::string("[gitcmdline] ") + cmd);
     182           3 :     cmd_start();
     183           3 :     const int rc = std::system(cmd.c_str());
     184           3 :     cmd_end();
     185           3 :     if (rc != 0) return ESYSREPO_RESULT(ResultCode::RAW_INT_ERROR, rc, cmd);
     186           3 :     return ESYSREPO_RESULT(ResultCode::OK);
     187           3 : }
     188             : 
     189          10 : void Git::feed_progress_stderr(std::string &pending, const char *data, std::size_t len)
     190             : {
     191          10 :     pending.append(data, len);
     192          10 :     std::size_t start = 0;
     193        2715 :     for (std::size_t i = 0; i < pending.size(); ++i)
     194             :     {
     195        2705 :         const char c = pending[i];
     196        2705 :         if (c != '\r' && c != '\n') continue;
     197          88 :         if (i > start) handle_sideband_progress(pending.substr(start, i - start));
     198          44 :         start = i + 1;
     199             :     }
     200          10 :     if (start > 0) pending.erase(0, start);
     201          10 : }
     202             : 
     203          11 : Result Git::run_git_progress(const std::vector<std::string> &args, const std::string &cwd)
     204             : {
     205          11 :     const std::string git = find_git_executable();
     206          11 :     if (git.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "git executable not found");
     207             : 
     208             :     // Merge stderr into the pipe so ``--progress`` lines (CR-updated) are readable.
     209          11 :     std::string cmd = build_git_command(args, cwd);
     210          11 :     cmd += " 2>&1";
     211             : 
     212          22 :     debug(1, std::string("[gitcmdline] ") + cmd);
     213          11 :     cmd_start();
     214          11 :     FILE *pipe = ESYSREPO_POPEN(cmd.c_str(), "r");
     215          11 :     if (pipe == nullptr)
     216             :     {
     217           0 :         cmd_end();
     218           0 :         return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "failed to start git");
     219             :     }
     220             : 
     221          11 :     std::string pending;
     222          11 :     char buf[1024];
     223          11 :     while (true)
     224             :     {
     225          11 :         const std::size_t n = std::fread(buf, 1, sizeof(buf), pipe);
     226          11 :         if (n > 0) feed_progress_stderr(pending, buf, n);
     227          11 :         if (n < sizeof(buf))
     228             :         {
     229          11 :             if (std::feof(pipe)) break;
     230           0 :             if (std::ferror(pipe)) break;
     231             :         }
     232             :     }
     233          11 :     if (!pending.empty()) handle_sideband_progress(pending);
     234             : 
     235          11 :     const int rc = ESYSREPO_PCLOSE(pipe);
     236          11 :     cmd_end();
     237             : 
     238          11 :     git::Progress done;
     239          11 :     done.set_done(true);
     240          11 :     done.set_percentage(git::Progress::MAX_PERCENTAGE);
     241          11 :     handle_transfer_progress(done);
     242             : 
     243          11 :     if (rc != 0) return ESYSREPO_RESULT(ResultCode::RAW_INT_ERROR, rc, cmd);
     244          11 :     return ESYSREPO_RESULT(ResultCode::OK);
     245          22 : }
     246             : 
     247          18 : Result Git::run_git_out(const std::vector<std::string> &args, const std::string &cwd, std::string &output)
     248             : {
     249          18 :     output.clear();
     250          18 :     const std::string git = find_git_executable();
     251          18 :     if (git.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "git executable not found");
     252             : 
     253             :     // Capture stdout; discard stderr so porcelain/status noise does not pollute parsing.
     254          18 :     std::string cmd = build_git_command(args, cwd);
     255             : #ifdef _WIN32
     256             :     cmd += " 2>NUL";
     257             : #else
     258          18 :     cmd += " 2>/dev/null";
     259             : #endif
     260             : 
     261          36 :     debug(1, std::string("[gitcmdline] ") + cmd);
     262          18 :     cmd_start();
     263          18 :     FILE *pipe = ESYSREPO_POPEN(cmd.c_str(), "r");
     264          18 :     if (pipe == nullptr)
     265             :     {
     266           0 :         cmd_end();
     267           0 :         return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "failed to start git");
     268             :     }
     269             :     char buf[4096];
     270          35 :     while (std::fgets(buf, sizeof(buf), pipe) != nullptr) output += buf;
     271          18 :     const int rc = ESYSREPO_PCLOSE(pipe);
     272          18 :     cmd_end();
     273          18 :     if (rc != 0) return ESYSREPO_RESULT(ResultCode::RAW_INT_ERROR, rc, cmd);
     274          18 :     return ESYSREPO_RESULT(ResultCode::OK);
     275          18 : }
     276             : 
     277           8 : Result Git::open(const std::string &folder)
     278             : {
     279           8 :     if (!is_repo(folder)) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "not a git repository");
     280           8 :     m_folder = folder;
     281           8 :     m_open = true;
     282           8 :     open_time();
     283           8 :     return ESYSREPO_RESULT(ResultCode::OK);
     284             : }
     285             : 
     286           9 : bool Git::is_open()
     287             : {
     288           9 :     return m_open;
     289             : }
     290             : 
     291           8 : Result Git::close()
     292             : {
     293           8 :     m_open = false;
     294           8 :     m_folder.clear();
     295           8 :     close_time();
     296           8 :     return ESYSREPO_RESULT(ResultCode::OK);
     297             : }
     298             : 
     299           0 : void Git::close_on_error()
     300             : {
     301           0 :     if (m_open) close();
     302           0 : }
     303             : 
     304           7 : Result Git::clone(const std::string &url, const std::string &path, const std::string &rev,
     305             :                   const git::CloneOptions &options)
     306             : {
     307           7 :     if (url.empty() || path.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty url or path");
     308             : 
     309          21 :     std::vector<std::string> args = {"clone", "--progress"};
     310             :     // Default CloneOptions: ignore rev (legacy remote-default tip only).
     311           7 :     if (options.checkout_rev && !rev.empty())
     312             :     {
     313           1 :         args.emplace_back("--branch");
     314           1 :         args.push_back(rev);
     315             :     }
     316           7 :     if (options.checkout_rev && options.single_branch) args.emplace_back("--single-branch");
     317           7 :     args.push_back(url);
     318           7 :     args.push_back(path);
     319             : 
     320           7 :     auto result = run_git_progress(args);
     321           7 :     if (result.error()) return ESYSREPO_RESULT(result);
     322             : 
     323           7 :     return open(path);
     324           7 : }
     325             : 
     326           6 : Result Git::fetch(const std::string &remote)
     327             : {
     328           6 :     if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
     329             : 
     330          12 :     std::vector<std::string> args = {"fetch", "--progress"};
     331           4 :     if (!remote.empty()) args.push_back(remote);
     332           4 :     return run_git_progress(args, m_folder);
     333           4 : }
     334             : 
     335           0 : Result Git::init_bare(const std::string &folder_path)
     336             : {
     337           0 :     if (folder_path.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty path");
     338           0 :     auto result = run_git({"init", "--bare", folder_path});
     339           0 :     if (result.error()) return ESYSREPO_RESULT(result);
     340           0 :     return open(folder_path);
     341           0 : }
     342             : 
     343           0 : Result Git::init(const std::string &folder_path)
     344             : {
     345           0 :     if (folder_path.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty path");
     346           0 :     auto result = run_git({"init", folder_path});
     347           0 :     if (result.error()) return ESYSREPO_RESULT(result);
     348           0 :     return open(folder_path);
     349           0 : }
     350             : 
     351           0 : Result Git::get_remotes(std::vector<git::Remote> &)
     352             : {
     353           0 :     return not_implemented();
     354             : }
     355             : 
     356           0 : Result Git::get_head_branch_remote(git::Branch &, git::Remote &)
     357             : {
     358           0 :     return not_implemented();
     359             : }
     360             : 
     361           0 : Result Git::add_remote(const std::string &name, const std::string &url)
     362             : {
     363           0 :     if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
     364           0 :     if (name.empty() || url.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty name or url");
     365           0 :     return run_git({"remote", "add", name, url}, m_folder);
     366             : }
     367             : 
     368           3 : Result Git::get_branches(git::Branches &branches, git::BranchType branch_type)
     369             : {
     370           3 :     if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
     371           3 :     branches.clear();
     372             : 
     373           3 :     if (branch_type == git::BranchType::LOCAL || branch_type == git::BranchType::ALL)
     374             :     {
     375           3 :         std::string head_name;
     376          15 :         auto result = run_git_out({"rev-parse", "--abbrev-ref", "HEAD"}, m_folder, head_name);
     377           3 :         if (result.error()) return ESYSREPO_RESULT(result);
     378           3 :         head_name = trim(head_name);
     379           3 :         if (head_name == "HEAD") head_name.clear(); // detached
     380             : 
     381           3 :         std::string out;
     382           9 :         result = run_git_out({"show-ref", "--heads"}, m_folder, out);
     383             :         // Empty repo / no heads: not an error for listing.
     384           3 :         if (result.ok())
     385             :         {
     386           6 :             for (const auto &line : split_lines(out))
     387             :             {
     388             :                 // <hash> <sp> refs/heads/<name>
     389           3 :                 const auto sp = line.find(' ');
     390           3 :                 if (sp == std::string::npos) continue;
     391           3 :                 const std::string ref = trim(line.substr(sp + 1));
     392           3 :                 const std::string heads = "refs/heads/";
     393           3 :                 if (ref.compare(0, heads.size(), heads) != 0) continue;
     394           3 :                 const std::string name = ref.substr(heads.size());
     395             : 
     396           3 :                 auto branch = std::make_shared<git::Branch>();
     397           3 :                 branch->set_ref_name(ref);
     398           3 :                 branch->set_name(name);
     399           3 :                 branch->set_type(git::BranchType::LOCAL);
     400           3 :                 branch->set_is_head(!head_name.empty() && name == head_name);
     401             : 
     402           3 :                 std::string remote;
     403           3 :                 std::string merge;
     404           3 :                 std::string cfg_out;
     405          12 :                 if (run_git_out({"config", "--get", "branch." + name + ".remote"}, m_folder, cfg_out).ok())
     406           3 :                     remote = trim(cfg_out);
     407          12 :                 if (run_git_out({"config", "--get", "branch." + name + ".merge"}, m_folder, cfg_out).ok())
     408           3 :                     merge = trim(cfg_out);
     409           3 :                 if (!remote.empty() && !merge.empty())
     410             :                 {
     411           3 :                     branch->set_remote_name(remote);
     412             :                     // Prefer refs/remotes/<remote>/<branch> for merge_analysis tips.
     413           3 :                     const std::string merge_heads = "refs/heads/";
     414           3 :                     std::string short_merge = merge;
     415           3 :                     if (merge.compare(0, merge_heads.size(), merge_heads) == 0)
     416           3 :                         short_merge = merge.substr(merge_heads.size());
     417           6 :                     branch->set_remote_branch("refs/remotes/" + remote + "/" + short_merge);
     418           3 :                 }
     419           9 :                 branches.add(branch);
     420           9 :             }
     421             :         }
     422           3 :     }
     423             : 
     424           3 :     if (branch_type == git::BranchType::REMOTE || branch_type == git::BranchType::ALL)
     425             :     {
     426           0 :         std::string out;
     427           0 :         auto result = run_git_out({"show-ref"}, m_folder, out);
     428           0 :         if (result.ok())
     429             :         {
     430           0 :             for (const auto &line : split_lines(out))
     431             :             {
     432           0 :                 const auto sp = line.find(' ');
     433           0 :                 if (sp == std::string::npos) continue;
     434           0 :                 const std::string ref = trim(line.substr(sp + 1));
     435           0 :                 const std::string remotes = "refs/remotes/";
     436           0 :                 if (ref.compare(0, remotes.size(), remotes) != 0) continue;
     437           0 :                 if (ref.size() >= 5 && ref.compare(ref.size() - 5, 5, "/HEAD") == 0) continue;
     438             : 
     439           0 :                 auto branch = std::make_shared<git::Branch>();
     440           0 :                 branch->set_ref_name(ref);
     441           0 :                 branch->set_name(ref.substr(remotes.size()));
     442           0 :                 branch->set_type(git::BranchType::REMOTE);
     443           0 :                 branches.add(branch);
     444           0 :             }
     445             :         }
     446           0 :     }
     447             : 
     448           3 :     return ESYSREPO_RESULT(ResultCode::OK);
     449             : }
     450             : 
     451           1 : Result Git::checkout(const std::string &branch, bool force)
     452             : {
     453           1 :     if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
     454           1 :     if (branch.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty branch");
     455             : 
     456           2 :     std::vector<std::string> args = {"checkout"};
     457           1 :     if (force) args.emplace_back("-f");
     458           2 :     args.push_back(strip_rev_prefix(branch));
     459           1 :     return run_git(args, m_folder);
     460           1 : }
     461             : 
     462           0 : Result Git::reset(const git::CommitHash &, git::ResetType)
     463             : {
     464           0 :     return not_implemented();
     465             : }
     466             : 
     467           1 : Result Git::fastforward(const git::CommitHash &commit)
     468             : {
     469           1 :     if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
     470           1 :     if (commit.get_hash().empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty commit");
     471             :     // Matches libgit2 path: move HEAD + worktree to the tip (FF only).
     472           4 :     return run_git({"merge", "--ff-only", commit.get_hash()}, m_folder);
     473             : }
     474             : 
     475           0 : Result Git::get_last_commit(git::CommitHash &)
     476             : {
     477           0 :     return not_implemented();
     478             : }
     479             : 
     480           0 : Result Git::get_last_commit(git::Commit &, bool)
     481             : {
     482           0 :     return not_implemented();
     483             : }
     484             : 
     485           0 : Result Git::get_parent_commit(const git::CommitHash &, git::CommitHash &, int)
     486             : {
     487           0 :     return not_implemented();
     488             : }
     489             : 
     490           1 : Result Git::is_dirty(bool &dirty)
     491             : {
     492           1 :     if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
     493           1 :     dirty = false;
     494           1 :     std::string out;
     495           4 :     auto result = run_git_out({"status", "--porcelain"}, m_folder, out);
     496           1 :     if (result.error()) return ESYSREPO_RESULT(result);
     497           1 :     dirty = !trim(out).empty();
     498           1 :     return ESYSREPO_RESULT(ResultCode::OK);
     499           2 : }
     500             : 
     501           1 : Result Git::is_detached(bool &detached)
     502             : {
     503           1 :     if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
     504           1 :     detached = false;
     505             :     // symbolic-ref fails (nonzero) when HEAD is detached.
     506           5 :     auto result = run_git({"symbolic-ref", "-q", "HEAD"}, m_folder);
     507           1 :     detached = result.error();
     508           1 :     return ESYSREPO_RESULT(ResultCode::OK);
     509           1 : }
     510             : 
     511           0 : Result Git::get_status(git::RepoStatus &)
     512             : {
     513           0 :     return not_implemented();
     514             : }
     515             : 
     516           0 : Result_t<bool> Git::is_ssh_agent_running(bool log_once)
     517             : {
     518           0 :     detect_ssh_agent(log_once);
     519           0 :     return ESYSREPO_RESULT_T(ResultCode::OK, s_ssh_agent_running);
     520             : }
     521             : 
     522           0 : void Git::detect_ssh_agent(bool /*log_once*/)
     523             : {
     524           0 :     if (s_detect_ssh_agent_done) return;
     525           0 :     s_detect_ssh_agent_done = true;
     526             : #ifdef _WIN32
     527             :     // Pageant / Windows OpenSSH agent: presence of SSH_AUTH_SOCK or ssh-agent service is enough for CLI git.
     528             :     const char *sock = std::getenv("SSH_AUTH_SOCK");
     529             :     s_ssh_agent_running = (sock != nullptr && sock[0] != '\0') || (std::getenv("SSH_AGENT_PID") != nullptr);
     530             : #else
     531           0 :     const char *sock = std::getenv("SSH_AUTH_SOCK");
     532           0 :     s_ssh_agent_running = (sock != nullptr && sock[0] != '\0');
     533             : #endif
     534             : }
     535             : 
     536           2 : Result Git::merge_analysis(const std::vector<std::string> &refs, git::MergeAnalysisResult &merge_analysis_result,
     537             :                            std::vector<git::CommitHash> &commits)
     538             : {
     539           2 :     if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
     540           2 :     merge_analysis_result = git::MergeAnalysisResult::NOT_SET;
     541           2 :     commits.clear();
     542           2 :     if (refs.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty refs");
     543             : 
     544           2 :     std::string head;
     545           8 :     auto result = run_git_out({"rev-parse", "HEAD"}, m_folder, head);
     546           2 :     if (result.error()) return ESYSREPO_RESULT(result);
     547           2 :     head = trim(head);
     548             : 
     549             :     // Sync only uses the first upstream ref today; analyze that tip.
     550           2 :     const std::string &ref = refs.front();
     551           2 :     std::string target;
     552           6 :     result = run_git_out({"rev-parse", ref}, m_folder, target);
     553           2 :     if (result.error()) return ESYSREPO_RESULT(result);
     554           2 :     target = trim(target);
     555             : 
     556           2 :     git::CommitHash target_commit;
     557           2 :     target_commit.set_hash(target);
     558           2 :     commits.push_back(target_commit);
     559             : 
     560           2 :     if (head == target)
     561             :     {
     562           1 :         merge_analysis_result = git::MergeAnalysisResult::UP_TO_DATE;
     563           1 :         return ESYSREPO_RESULT(ResultCode::OK);
     564             :     }
     565             : 
     566           1 :     std::string base;
     567           4 :     result = run_git_out({"merge-base", "HEAD", ref}, m_folder, base);
     568           1 :     if (result.error()) return ESYSREPO_RESULT(result);
     569           1 :     base = trim(base);
     570             : 
     571           1 :     if (base == head)
     572           1 :         merge_analysis_result = git::MergeAnalysisResult::FASTFORWARD;
     573           0 :     else if (base == target)
     574           0 :         merge_analysis_result = git::MergeAnalysisResult::UP_TO_DATE;
     575             :     else
     576           0 :         merge_analysis_result = git::MergeAnalysisResult::NORMAL;
     577             : 
     578           1 :     return ESYSREPO_RESULT(ResultCode::OK);
     579           6 : }
     580             : 
     581           0 : Result Git::fetch_all_notes(const std::string &)
     582             : {
     583           0 :     return not_implemented();
     584             : }
     585             : 
     586           1 : Result_t<bool> Git::has_branch(const std::string &name, git::BranchType branch_type)
     587             : {
     588           1 :     if (!m_open) return ESYSREPO_RESULT_T(ResultCode::GIT_GENERIC_ERROR, false);
     589           1 :     git::Branches branches;
     590           1 :     auto result = get_branches(branches, branch_type);
     591           1 :     if (result.error()) return ESYSREPO_RESULT_T(result, false);
     592           1 :     const std::string tip = strip_rev_prefix(name);
     593           1 :     for (const auto &b : branches.get())
     594             :     {
     595           1 :         if (b->get_name() == tip || b->get_name() == name || b->get_ref_name() == name)
     596           1 :             return ESYSREPO_RESULT_T(ResultCode::OK, true);
     597             :     }
     598           0 :     return ESYSREPO_RESULT_T(ResultCode::OK, false);
     599           1 : }
     600             : 
     601           0 : Result Git::get_hash(const std::string &revision, std::string &hash, git::BranchType /*branch_type*/)
     602             : {
     603           0 :     if (!m_open) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "repository not open");
     604           0 :     hash.clear();
     605           0 :     std::string out;
     606           0 :     auto result = run_git_out({"rev-parse", revision}, m_folder, out);
     607           0 :     if (result.error()) return ESYSREPO_RESULT(result);
     608           0 :     hash = trim(out);
     609           0 :     return ESYSREPO_RESULT(ResultCode::OK);
     610           0 : }
     611             : 
     612           0 : Result Git::walk_commits(std::shared_ptr<git::WalkCommit>)
     613             : {
     614           0 :     return not_implemented();
     615             : }
     616             : 
     617           0 : Result Git::diff(const git::CommitHash &, std::shared_ptr<git::Diff>)
     618             : {
     619           0 :     return not_implemented();
     620             : }
     621             : 
     622           0 : Result Git::get_ahead_behind(git::AheadBehind &, const git::Branch &)
     623             : {
     624           0 :     return not_implemented();
     625             : }
     626             : 
     627           0 : Result Git::get_ahead_behind(git::AheadBehind &, const std::string &, const std::string &)
     628             : {
     629           0 :     return not_implemented();
     630             : }
     631             : 
     632           0 : Result Git::add_note(git::NoteId &, const std::string &, const std::string &, bool)
     633             : {
     634           0 :     return not_implemented();
     635             : }
     636             : 
     637           0 : Result Git::remove_note(const git::NoteId &)
     638             : {
     639           0 :     return not_implemented();
     640             : }
     641             : 
     642           0 : Result Git::remove_note(const git::CommitHash &, const std::string &)
     643             : {
     644           0 :     return not_implemented();
     645             : }
     646             : 
     647           0 : Result Git::remove_note_last_commit(const std::string &)
     648             : {
     649           0 :     return not_implemented();
     650             : }
     651             : 
     652           0 : Result Git::push(const std::string &, const std::string &, const std::string &)
     653             : {
     654           0 :     return not_implemented();
     655             : }
     656             : 
     657           0 : Result Git::push_notes(const std::string &, const std::string &)
     658             : {
     659           0 :     return not_implemented();
     660             : }
     661             : 
     662           0 : const std::string &Git::get_version()
     663             : {
     664           0 :     return s_get_version();
     665             : }
     666             : 
     667           0 : const std::string &Git::get_lib_name()
     668             : {
     669           0 :     return s_get_lib_name();
     670             : }
     671             : 
     672           0 : const std::string &Git::s_get_version()
     673             : {
     674           0 :     static const std::string version = "git-cmdline";
     675           0 :     return version;
     676             : }
     677             : 
     678           0 : const std::string &Git::s_get_lib_name()
     679             : {
     680           0 :     static const std::string name = "gitcmdline";
     681           0 :     return name;
     682             : }
     683             : 
     684           0 : bool Git::do_is_ssh_backend_supported(git::SshBackend backend) const
     685             : {
     686           0 :     return backend == git::SshBackend::Exec;
     687             : }
     688             : 
     689           0 : bool Git::do_is_ssh_backend_available(git::SshBackend backend, bool /*force*/)
     690             : {
     691           0 :     if (backend != git::SshBackend::Exec) return false;
     692           0 :     return !find_git_executable().empty();
     693             : }
     694             : 
     695           0 : Result Git::do_set_ssh_backend(git::SshBackend backend)
     696             : {
     697           0 :     if (backend != git::SshBackend::Exec)
     698           0 :         return ESYSREPO_RESULT(ResultCode::GIT_SSH_BACKEND_NOT_SUPPORTED);
     699           0 :     m_ssh_backend = backend;
     700           0 :     return ESYSREPO_RESULT(ResultCode::OK);
     701             : }
     702             : 
     703           0 : Result_t<git::SshBackend> Git::do_get_ssh_backend() const
     704             : {
     705           0 :     return ESYSREPO_RESULT_T(ResultCode::OK, m_ssh_backend);
     706             : }
     707             : 
     708             : } // namespace esys::repo::gitcmdline

Generated by: LCOV version 1.14