LCOV - code coverage report
Current view: top level - src/esys/repo/libgit2 - git_libgit2.cpp (source / functions) Hit Total Coverage
Test: esysrepo_coverage.info Lines: 159 187 85.0 %
Date: 2026-08-01 10:43:40 Functions: 54 62 87.1 %

          Line data    Source code
       1             : /*!
       2             :  * \file esys/repo/libgit2/git_libgit2.cpp
       3             :  * \brief
       4             :  *
       5             :  * \cond
       6             :  * __legal_b__
       7             :  *
       8             :  * Copyright (c) 2020-2023 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/libgit2/git.h"
      20             : #include "esys/repo/libgit2/gitimpl.h"
      21             : 
      22             : #include <git2.h>
      23             : 
      24             : #include <boost/filesystem.hpp>
      25             : #include <boost/process/search_path.hpp>
      26             : 
      27             : #include <cstdlib>
      28             : #include <string>
      29             : #include <string_view>
      30             : 
      31             : namespace esys::repo::libgit2
      32             : {
      33             : 
      34             : bool Git::s_detect_ssh_agent_done = false;
      35             : bool Git::s_ssh_agent_running = false;
      36             : bool Git::s_exec_probe_done = false;
      37             : bool Git::s_exec_available = false;
      38             : int Git::s_libssh2_supported = -1;
      39             : int Git::s_exec_supported = -1;
      40             : 
      41         145 : std::shared_ptr<GitBase> Git::new_ptr()
      42             : {
      43         145 :     return std::make_shared<Git>();
      44             : }
      45             : 
      46         203 : Git::Git()
      47         203 :     : GitBase()
      48             : {
      49         203 :     m_impl = std::make_unique<GitImpl>(this);
      50         203 : }
      51             : 
      52         203 : Git::~Git() = default;
      53             : 
      54         143 : Result Git::open(const std::string &folder)
      55             : {
      56         143 :     set_folder(folder);
      57         143 :     return get_impl()->open(folder);
      58             : }
      59             : 
      60         268 : bool Git::is_open()
      61             : {
      62         268 :     return get_impl()->is_open();
      63             : }
      64             : 
      65         241 : Result Git::close()
      66             : {
      67         241 :     return get_impl()->close();
      68             : }
      69             : 
      70           3 : Result Git::init_bare(const std::string &folder_path)
      71             : {
      72           3 :     return get_impl()->init_bare(folder_path);
      73             : }
      74             : 
      75           1 : Result Git::init(const std::string &folder_path)
      76             : {
      77           1 :     return get_impl()->init(folder_path);
      78             : }
      79             : 
      80           0 : void Git::close_on_error()
      81             : {
      82           0 :     return get_impl()->close_on_error();
      83             : }
      84             : 
      85           4 : Result Git::get_remotes(std::vector<git::Remote> &remotes)
      86             : {
      87           4 :     return get_impl()->get_remotes(remotes);
      88             : }
      89             : 
      90           7 : Result Git::get_head_branch_remote(git::Branch &branch, git::Remote &remote)
      91             : {
      92           7 :     return get_impl()->get_head_branch_remote(branch, remote);
      93             : }
      94             : 
      95           5 : Result Git::add_remote(const std::string &name, const std::string &url)
      96             : {
      97           5 :     return get_impl()->add_remote(name, url);
      98             : }
      99             : 
     100          95 : Result Git::get_branches(git::Branches &branches, git::BranchType branch_type)
     101             : {
     102          95 :     return get_impl()->get_branches(branches, branch_type);
     103             : }
     104             : 
     105         150 : Result Git::clone(const std::string &url, const std::string &path, const std::string &rev,
     106             :                   const git::CloneOptions &options)
     107             : {
     108         150 :     set_url(url);
     109         150 :     return get_impl()->clone(url, path, rev, options);
     110             : }
     111             : 
     112          74 : Result Git::checkout(const std::string &branch, bool force)
     113             : {
     114          74 :     return get_impl()->checkout(branch, force);
     115             : }
     116             : 
     117           2 : Result Git::reset(const git::CommitHash &commit, git::ResetType type)
     118             : {
     119           2 :     return get_impl()->reset(commit, type);
     120             : }
     121             : 
     122           2 : Result Git::fastforward(const git::CommitHash &commit)
     123             : {
     124           2 :     return get_impl()->fastforward(commit);
     125             : }
     126             : 
     127          29 : Result Git::get_last_commit(git::CommitHash &commit)
     128             : {
     129          29 :     return get_impl()->get_last_commit(commit);
     130             : }
     131             : 
     132           7 : Result Git::get_last_commit(git::Commit &commit, bool get_all_notes)
     133             : {
     134           7 :     return get_impl()->get_last_commit(commit, get_all_notes);
     135             : }
     136             : 
     137           5 : Result Git::get_parent_commit(const git::CommitHash &commit, git::CommitHash &parent, int nth_parent)
     138             : {
     139           5 :     return get_impl()->get_parent_commit(commit, parent, nth_parent);
     140             : }
     141             : 
     142          30 : Result Git::is_dirty(bool &dirty)
     143             : {
     144          30 :     return get_impl()->is_dirty(dirty);
     145             : }
     146             : 
     147          26 : Result Git::is_detached(bool &detached)
     148             : {
     149          26 :     return get_impl()->is_detached(detached);
     150             : }
     151             : 
     152          31 : Result Git::get_status(git::RepoStatus &repo_status)
     153             : {
     154          31 :     return m_impl->get_status(repo_status);
     155             : }
     156             : 
     157          20 : Result_t<bool> Git::is_ssh_agent_running(bool log_once)
     158             : {
     159          20 :     if (!s_detect_ssh_agent_done) detect_ssh_agent(log_once);
     160             : 
     161          20 :     return s_ssh_agent_running;
     162             : }
     163             : 
     164          20 : void Git::detect_ssh_agent(bool log_once)
     165             : {
     166          20 :     if (s_detect_ssh_agent_done) return;
     167             : 
     168           1 :     s_detect_ssh_agent_done = true;
     169           1 :     s_ssh_agent_running = m_impl->is_ssh_agent_running();
     170             : 
     171           2 :     if (s_ssh_agent_running && log_once) info("SSH agent detected");
     172             : }
     173             : 
     174          11 : bool Git::do_is_ssh_backend_supported(git::SshBackend backend) const
     175             : {
     176          11 :     if (!(git_libgit2_features() & GIT_FEATURE_SSH)) return false;
     177             : 
     178          11 :     switch (backend)
     179             :     {
     180           7 :         case git::SshBackend::LibSSH2: return probe_ssh_backend_compiled("libssh2", s_libssh2_supported);
     181           4 :         case git::SshBackend::Exec: return probe_ssh_backend_compiled("exec", s_exec_supported);
     182             :     }
     183             :     return false;
     184             : }
     185             : 
     186          11 : bool Git::probe_ssh_backend_compiled(const char *name, int &cache)
     187             : {
     188          11 :     if (cache >= 0) return cache != 0;
     189             : 
     190           2 :     git_buf previous = GIT_BUF_INIT;
     191           2 :     const bool had_previous = (git_libgit2_opts(GIT_OPT_GET_SSH_BACKEND, &previous) == 0);
     192             : 
     193           2 :     const int set_error = git_libgit2_opts(GIT_OPT_SET_SSH_BACKEND, name);
     194           2 :     cache = (set_error == 0) ? 1 : 0;
     195             : 
     196           2 :     if (had_previous && previous.ptr != nullptr)
     197           2 :         git_libgit2_opts(GIT_OPT_SET_SSH_BACKEND, previous.ptr);
     198           0 :     else if (set_error == 0)
     199             :         // Do not leave a temporary probe selection as the process-wide backend
     200             :         // when there was no prior GET value to restore.
     201           0 :         git_libgit2_opts(GIT_OPT_SET_SSH_BACKEND, "libssh2");
     202             : 
     203           2 :     git_buf_dispose(&previous);
     204           2 :     return cache != 0;
     205             : }
     206             : 
     207             : namespace
     208             : {
     209             : 
     210           2 : std::string get_env_var(const char *name)
     211             : {
     212             : #ifdef _WIN32
     213             :     char *buffer = nullptr;
     214             :     size_t length = 0;
     215             :     if (_dupenv_s(&buffer, &length, name) != 0 || buffer == nullptr) return {};
     216             :     std::string value(buffer);
     217             :     free(buffer);
     218             :     return value;
     219             : #else
     220           2 :     const char *value = std::getenv(name);
     221           2 :     return value != nullptr ? std::string(value) : std::string();
     222             : #endif
     223             : }
     224             : 
     225             : } // namespace
     226             : 
     227           1 : bool Git::probe_openssh_client()
     228             : {
     229           2 :     auto command_exists = [](const std::string &command) -> bool {
     230           1 :         if (command.empty()) return false;
     231             : 
     232           1 :         boost::filesystem::path path(command);
     233           2 :         if (path.is_absolute() || path.has_parent_path()) return boost::filesystem::exists(path);
     234             : 
     235             : #ifdef _WIN32
     236             :         boost::filesystem::path found = boost::process::search_path(path.filename().string());
     237             : #else
     238           2 :         boost::filesystem::path found = boost::process::search_path(command);
     239             : #endif
     240           1 :         return !found.empty();
     241           2 :     };
     242             : 
     243           1 :     const std::string ssh_command = get_env_var("GIT_SSH_COMMAND");
     244           1 :     if (!ssh_command.empty())
     245             :     {
     246             :         // Shell command line: verify the first token looks findable when possible.
     247           0 :         auto start = ssh_command.find_first_not_of(" \t");
     248           0 :         if (start == std::string::npos) return true;
     249           0 :         auto end = ssh_command.find_first_of(" \t", start);
     250           0 :         const std::string first_token =
     251           0 :             ssh_command.substr(start, end == std::string::npos ? std::string::npos : end - start);
     252           0 :         if (first_token.empty()) return true;
     253           0 :         return command_exists(first_token);
     254           0 :     }
     255             : 
     256           1 :     const std::string ssh = get_env_var("GIT_SSH");
     257           1 :     if (!ssh.empty()) return command_exists(ssh);
     258             : 
     259             : #ifdef _WIN32
     260             :     return command_exists("ssh.exe") || command_exists("ssh");
     261             : #else
     262           1 :     return command_exists("ssh");
     263             : #endif
     264           1 : }
     265             : 
     266           3 : bool Git::do_is_ssh_backend_available(git::SshBackend backend, bool force)
     267             : {
     268           3 :     if (!do_is_ssh_backend_supported(backend)) return false;
     269             : 
     270           3 :     if (backend == git::SshBackend::LibSSH2) return true;
     271             : 
     272           2 :     if (force) s_exec_probe_done = false;
     273             : 
     274           2 :     if (!s_exec_probe_done)
     275             :     {
     276           1 :         s_exec_probe_done = true;
     277           1 :         s_exec_available = probe_openssh_client();
     278             :     }
     279             : 
     280           2 :     return s_exec_available;
     281             : }
     282             : 
     283           5 : Result Git::do_set_ssh_backend(git::SshBackend backend)
     284             : {
     285           5 :     if (!do_is_ssh_backend_supported(backend))
     286           0 :         return ESYSREPO_RESULT(ResultCode::GIT_SSH_BACKEND_NOT_SUPPORTED);
     287             : 
     288           5 :     if (backend == git::SshBackend::Exec && !do_is_ssh_backend_available(backend, false))
     289           0 :         return ESYSREPO_RESULT(ResultCode::GIT_SSH_BACKEND_NOT_SUPPORTED, "OpenSSH client not found");
     290             : 
     291           5 :     const char *name = (backend == git::SshBackend::Exec) ? "exec" : "libssh2";
     292           5 :     int error = git_libgit2_opts(GIT_OPT_SET_SSH_BACKEND, name);
     293           5 :     if (error < 0) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, error);
     294             : 
     295           5 :     return ESYSREPO_RESULT(ResultCode::OK);
     296             : }
     297             : 
     298         195 : Result_t<git::SshBackend> Git::do_get_ssh_backend() const
     299             : {
     300         195 :     if (!(git_libgit2_features() & GIT_FEATURE_SSH))
     301           0 :         return ESYSREPO_RESULT_T(ResultCode::GIT_SSH_BACKEND_NOT_SUPPORTED, git::SshBackend::LibSSH2);
     302             : 
     303         195 :     git_buf buf = GIT_BUF_INIT;
     304         195 :     int error = git_libgit2_opts(GIT_OPT_GET_SSH_BACKEND, &buf);
     305         195 :     if (error < 0)
     306           0 :         return ESYSREPO_RESULT_T(ResultCode::GIT_GENERIC_ERROR, git::SshBackend::LibSSH2);
     307             : 
     308         195 :     git::SshBackend backend = git::SshBackend::LibSSH2;
     309         195 :     if (buf.ptr != nullptr && std::string_view(buf.ptr) == "exec") backend = git::SshBackend::Exec;
     310             : 
     311         195 :     git_buf_dispose(&buf);
     312         390 :     return backend;
     313             : }
     314             : 
     315          23 : Result Git::merge_analysis(const std::vector<std::string> &refs, git::MergeAnalysisResult &merge_analysis_result,
     316             :                            std::vector<git::CommitHash> &commits)
     317             : {
     318          23 :     return m_impl->merge_analysis(refs, merge_analysis_result, commits);
     319             : }
     320             : 
     321          68 : Result Git::fetch(const std::string &remote)
     322             : {
     323          68 :     return m_impl->fetch(remote);
     324             : }
     325             : 
     326           2 : Result Git::fetch_all_notes(const std::string &remote)
     327             : {
     328           2 :     return m_impl->fetch_all_notes(remote);
     329             : }
     330             : 
     331           4 : Result Git::add_note(git::NoteId &note_id, const std::string &reference, const std::string &message, bool overwrite)
     332             : {
     333           4 :     return m_impl->add_note(note_id, reference, message, overwrite);
     334             : }
     335             : 
     336           1 : Result Git::remove_note(const git::NoteId &note_id)
     337             : {
     338           1 :     return m_impl->remove_note(note_id);
     339             : }
     340             : 
     341           0 : Result Git::remove_note(const git::CommitHash &commit_hash, const std::string &reference)
     342             : {
     343           0 :     return m_impl->remove_note(commit_hash, reference);
     344             : }
     345             : 
     346           1 : Result Git::remove_note_last_commit(const std::string &reference)
     347             : {
     348           1 :     return m_impl->remove_note_last_commit(reference);
     349             : }
     350             : 
     351           2 : Result Git::push(const std::string &remote, const std::string &src_ref, const std::string &dst_ref)
     352             : {
     353           2 :     return m_impl->push(remote, src_ref, dst_ref);
     354             : }
     355             : 
     356           1 : Result Git::push_notes(const std::string &remote, const std::string &reference)
     357             : {
     358           1 :     return m_impl->push_notes(remote, reference);
     359             : }
     360             : 
     361          15 : Result_t<bool> Git::has_branch(const std::string &name, git::BranchType branch_type)
     362             : {
     363          15 :     return m_impl->has_branch(name, branch_type);
     364             : }
     365             : 
     366          27 : Result Git::get_hash(const std::string &revision, std::string &hash, git::BranchType branch_type)
     367             : {
     368          27 :     return m_impl->get_hash(revision, hash, branch_type);
     369             : }
     370             : 
     371           2 : Result Git::walk_commits(std::shared_ptr<git::WalkCommit> walk_commit)
     372             : {
     373           6 :     return m_impl->walk_commits(walk_commit);
     374             : }
     375             : 
     376           4 : Result Git::diff(const git::CommitHash &commit_hash, std::shared_ptr<git::Diff> diff)
     377             : {
     378          12 :     return m_impl->diff(commit_hash, diff);
     379             : }
     380             : 
     381           0 : Result Git::get_ahead_behind(git::AheadBehind &ahead_behind, const git::Branch &local_branch)
     382             : {
     383           0 :     return m_impl->get_ahead_behind(ahead_behind, local_branch);
     384             : }
     385             : 
     386           3 : Result Git::get_ahead_behind(git::AheadBehind &ahead_behind, const std::string &first_ref,
     387             :                              const std::string &second_ref)
     388             : {
     389           3 :     return m_impl->get_ahead_behind(ahead_behind, first_ref, second_ref);
     390             : }
     391             : 
     392         150 : void Git::set_url(const std::string &url)
     393             : {
     394         150 :     m_url = url;
     395         150 : }
     396             : 
     397           0 : const std::string &Git::get_url()
     398             : {
     399           0 :     return m_url;
     400             : }
     401             : 
     402         143 : void Git::set_folder(const std::string &folder)
     403             : {
     404         143 :     m_folder = folder;
     405         143 : }
     406             : 
     407           0 : const std::string &Git::get_folder()
     408             : {
     409           0 :     return m_folder;
     410             : }
     411             : 
     412           0 : const std::string &Git::get_version()
     413             : {
     414           0 :     return m_impl->get_version();
     415             : }
     416             : 
     417           0 : const std::string &Git::get_lib_name()
     418             : {
     419           0 :     return m_impl->get_lib_name();
     420             : }
     421             : 
     422         990 : void Git::debug(int level, const std::string &msg)
     423             : {
     424             :     // log::ConsoleLockGuard<log::User> lock(this);
     425             :     //! \TODO must be fixed to allow writing after all console is written
     426             : 
     427         990 :     GitBase::debug(level, msg);
     428         990 : }
     429             : 
     430           2 : const std::string &Git::s_get_version()
     431             : {
     432           2 :     return GitImpl::s_get_version();
     433             : }
     434             : 
     435           2 : const std::string &Git::s_get_lib_name()
     436             : {
     437           2 :     return GitImpl::s_get_lib_name();
     438             : }
     439             : 
     440           2 : const std::string &Git::s_get_ssh_version()
     441             : {
     442           2 :     return GitImpl::s_get_ssh_version();
     443             : }
     444             : 
     445           2 : const std::string &Git::s_get_ssh_lib_name()
     446             : {
     447           2 :     return GitImpl::s_get_ssh_lib_name();
     448             : }
     449             : 
     450        1543 : GitImpl *Git::get_impl() const
     451             : {
     452        1543 :     return m_impl.get();
     453             : }
     454             : 
     455         429 : void Git::set_logger_if(std::shared_ptr<log::Logger_if> logger_if)
     456             : {
     457         858 :     get_impl()->set_logger_if(logger_if);
     458         429 : }
     459             : 
     460             : } // namespace esys::repo::libgit2

Generated by: LCOV version 1.14