LCOV - code coverage report
Current view: top level - src/esys/repo/libgit2 - gitimpl_libgit2.cpp (source / functions) Hit Total Coverage
Test: esysrepo_coverage.info Lines: 1126 1392 80.9 %
Date: 2026-08-01 10:43:40 Functions: 76 90 84.4 %

          Line data    Source code
       1             : /*!
       2             :  * \file esys/repo/libgit2/gitimpl_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/gitimpl.h"
      20             : #include "esys/repo/libgit2/guard.h"
      21             : #include "esys/repo/libgit2/guards.h"
      22             : #include "esys/repo/git/sshbackend.h"
      23             : #include "esys/repo/git/updatetip.h"
      24             : #include "esys/repo/libssh2/ssh.h"
      25             : 
      26             : #include <esys/trace/call.h>
      27             : #include <esys/trace/macros.h>
      28             : 
      29             : #include <git2.h>
      30             : #include <libssh2.h>
      31             : 
      32             : #include <boost/algorithm/string.hpp>
      33             : #include <boost/filesystem.hpp>
      34             : 
      35             : #include <cstring>
      36             : #include <sstream>
      37             : #include <cassert>
      38             : #include <algorithm>
      39             : #include <cstdlib>
      40             : #include <mutex>
      41             : 
      42             : #include <iostream>
      43             : 
      44             : namespace esys::repo::libgit2
      45             : {
      46             : 
      47             : std::unique_ptr<LibGit2> GitImpl::s_libgt2 = nullptr;
      48             : 
      49         203 : GitImpl::GitImpl(Git *self)
      50         203 :     : m_self(self)
      51         203 :     , m_notes(self)
      52             : {
      53         204 :     if (s_libgt2 == nullptr) s_libgt2 = std::make_unique<LibGit2>();
      54         203 : }
      55             : 
      56         203 : GitImpl::~GitImpl()
      57             : {
      58         203 :     if (m_repo != nullptr) close_on_error();
      59         203 : }
      60             : 
      61         143 : Result GitImpl::open(const std::string &folder)
      62             : {
      63         143 :     Result result;
      64         143 :     ETRC_CALL_RET(result, folder);
      65             : 
      66         143 :     self()->open_time();
      67             : 
      68         143 :     int result_int = git_repository_open(&m_repo, folder.c_str());
      69         143 :     if (result_int < 0)
      70             :     {
      71           0 :         result = ESYSREPO_RESULT(ResultCode::GIT_ERROR_OPENING_REPO, "open git repo " + folder);
      72           0 :         return ETRC_RET(ESYSREPO_RESULT(check_error(result)));
      73             :     }
      74             : 
      75         286 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
      76         143 : }
      77             : 
      78         456 : bool GitImpl::is_open()
      79             : {
      80         456 :     return (m_repo != nullptr);
      81             : }
      82             : 
      83           3 : Result GitImpl::init_bare(const std::string &folder_path)
      84             : {
      85           3 :     Result result;
      86           3 :     ETRC_CALL_RET(result, folder_path);
      87             : 
      88           3 :     if (is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_ALREADY_OPENED));
      89             : 
      90           3 :     git_repository_init_options initopts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
      91           3 :     initopts.flags = GIT_REPOSITORY_INIT_MKPATH;
      92           3 :     initopts.flags |= GIT_REPOSITORY_INIT_BARE;
      93             : 
      94           3 :     int result_int = git_repository_init_ext(&m_repo, folder_path.c_str(), &initopts);
      95           3 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
      96             : 
      97           6 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
      98           3 : }
      99             : 
     100           1 : Result GitImpl::init(const std::string &folder_path)
     101             : {
     102           1 :     Result result;
     103           1 :     ETRC_CALL_RET(result, folder_path);
     104             : 
     105           1 :     if (is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_ALREADY_OPENED));
     106             : 
     107           1 :     self()->cmd_start();
     108             : 
     109           1 :     git_repository_init_options initopts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
     110           1 :     initopts.flags = GIT_REPOSITORY_INIT_MKPATH;
     111             : 
     112           1 :     int result_int = git_repository_init_ext(&m_repo, folder_path.c_str(), &initopts);
     113           1 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     114             : 
     115           2 :     return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     116           1 : }
     117             : 
     118         241 : Result GitImpl::close()
     119             : {
     120         241 :     Result result;
     121         241 :     ETRC_CALL_RET_NP(result);
     122             : 
     123         241 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
     124             : 
     125         241 :     git_repository_free(m_repo);
     126             : 
     127         241 :     m_repo = nullptr;
     128             : 
     129         241 :     self()->close_time();
     130             : 
     131         482 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     132         241 : }
     133             : 
     134          55 : void GitImpl::close_on_error()
     135             : {
     136          55 :     ETRC_CALL_NP();
     137          55 :     self()->close_time();
     138          55 :     if (m_repo == nullptr) return;
     139             : 
     140          55 :     git_repository_free(m_repo);
     141          55 :     m_repo = nullptr;
     142          55 : }
     143             : 
     144          64 : Result GitImpl::get_remotes(std::vector<git::Remote> &remotes)
     145             : {
     146          64 :     Result result;
     147          64 :     ETRC_CALL_RET_BEGIN(result, remotes);
     148          64 :     ETRC_CALL_RET_OUT_END(remotes);
     149             : 
     150          64 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
     151             : 
     152          64 :     self()->cmd_start();
     153             : 
     154          64 :     remotes.clear();
     155          64 :     GuardS<git_strarray> data;
     156             : 
     157          64 :     int result_int = git_remote_list(data.get(), m_repo);
     158          64 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     159             : 
     160          64 :     char **p = data.get()->strings;
     161             : 
     162          64 :     if (data.get()->count == 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     163             : 
     164          64 :     if (p == nullptr) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "No data")));
     165             : 
     166         130 :     for (auto idx = 0; idx < data.get()->count; ++idx)
     167             :     {
     168          66 :         char *name = *p;
     169          66 :         if (name == nullptr) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "No name")));
     170             : 
     171          66 :         git::Remote remote;
     172             : 
     173          66 :         remote.set_name(name);
     174             : 
     175          66 :         remotes.push_back(remote);
     176          66 :         p += 1;
     177          66 :     }
     178             : 
     179         130 :     for (auto &remote_item : remotes)
     180             :     {
     181          66 :         Guard<git_remote> remote; // This will automically release the git_remote
     182             : 
     183          66 :         const git_remote_head **refs = nullptr;
     184          66 :         size_t refs_len = 0;
     185          66 :         size_t i = 0;
     186          66 :         git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
     187             : 
     188             :         // Find the remote by name
     189          66 :         result_int = git_remote_lookup(remote.get_p(), m_repo, remote_item.get_name().c_str());
     190          66 :         if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     191             : 
     192          66 :         const char *url = git_remote_url(remote.get());
     193         132 :         remote_item.set_url(url);
     194             : 
     195             :         /*result = git_remote_ls(&refs, &refs_len, remote.get());
     196             :         if (result < 0) return check_error(result, ""); */
     197          66 :     }
     198             : 
     199         128 :     return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     200          64 : }
     201             : 
     202           7 : Result GitImpl::get_head_branch_remote(git::Branch &branch, git::Remote &remote)
     203             : {
     204           7 :     Result result;
     205           7 :     ETRC_CALL_RET_BEGIN(result, branch, remote);
     206           7 :     ETRC_CALL_RET_OUT_END(branch, remote);
     207             : 
     208           7 :     if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
     209             : 
     210           7 :     Guard<git_reference> head_ref;
     211             : 
     212           7 :     auto result_int = git_repository_head(head_ref.get_p(), m_repo);
     213           7 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
     214           7 :     auto head_ref_name = git_reference_name(head_ref.get());
     215           7 :     auto head_ref_short = git_reference_shorthand(head_ref.get());
     216             : 
     217           7 :     branch.set_is_head(true);
     218           7 :     branch.set_ref_name(head_ref_name);
     219           7 :     branch.set_name(head_ref_short);
     220           7 :     branch.set_type(git::BranchType::LOCAL);
     221             : 
     222           7 :     result_int = git_repository_head_detached(m_repo);
     223           7 :     if (result_int == 1)
     224           2 :         branch.set_detached(true);
     225           5 :     else if (result_int == 0)
     226           5 :         branch.set_detached(false);
     227           0 :     else if (result_int < 0)
     228           0 :         return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
     229             : 
     230           7 :     git_buf remote_name_buf = {nullptr};
     231           7 :     result_int = git_branch_upstream_remote(&remote_name_buf, m_repo, head_ref_name);
     232           9 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
     233             : 
     234          10 :     std::string remote_name(remote_name_buf.ptr, remote_name_buf.ptr + remote_name_buf.size);
     235           5 :     git_buf_dispose(&remote_name_buf);
     236             : 
     237           5 :     branch.set_remote_name(remote_name);
     238           5 :     remote.set_name(remote_name);
     239             : 
     240           5 :     Guard<git_remote> remote_git;
     241           5 :     result = find_remote(remote_git, remote_name.c_str());
     242           5 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     243             : 
     244           5 :     std::string remote_url = git_remote_url(remote_git.get());
     245           5 :     remote.set_url(remote_url);
     246             : 
     247           5 :     Guard<git_reference> upstream_ref;
     248           5 :     result_int = git_branch_upstream(upstream_ref.get_p(), head_ref.get());
     249           5 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
     250             : 
     251           5 :     auto up_ref_name = git_reference_name(upstream_ref.get());
     252           5 :     branch.set_remote_branch(up_ref_name);
     253           5 :     auto up_ref_short = git_reference_shorthand(upstream_ref.get());
     254           5 :     branch.set_remote_branch_name(up_ref_short);
     255          10 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     256          24 : }
     257             : 
     258           5 : Result GitImpl::add_remote(const std::string &name, const std::string &url)
     259             : {
     260           5 :     Result result;
     261           5 :     ETRC_CALL_RET(result, name, url);
     262             : 
     263           5 :     if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
     264             : 
     265           5 :     self()->cmd_start();
     266             : 
     267           5 :     Guard<git_remote> remote;
     268             : 
     269           5 :     int result_int = git_remote_create(remote.get_p(), m_repo, name.c_str(), url.c_str());
     270           5 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     271             : 
     272          10 :     return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     273           5 : }
     274             : 
     275         163 : Result GitImpl::get_branches(git::Branches &branches, git::BranchType branch_type)
     276             : {
     277         163 :     Result result;
     278         163 :     ETRC_CALL_RET_BEGIN(result, branches, branch_type);
     279         163 :     ETRC_CALL_RET_OUT_END(branches);
     280             : 
     281         163 :     if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
     282             : 
     283         163 :     self()->cmd_start();
     284             : 
     285         163 :     Guard<git_branch_iterator> branch_it;
     286         163 :     git_branch_t list_flags = GIT_BRANCH_ALL;
     287             : 
     288         163 :     convert(branch_type, list_flags);
     289             : 
     290         163 :     int result_int = git_branch_iterator_new(branch_it.get_p(), m_repo, list_flags);
     291         163 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     292             : 
     293         408 :     while (true)
     294             :     {
     295         408 :         Guard<git_reference> ref;
     296         408 :         git_branch_t git_branch_type = GIT_BRANCH_ALL;
     297             : 
     298         408 :         result_int = git_branch_next(ref.get_p(), &git_branch_type, branch_it.get());
     299         571 :         if (result_int == GIT_ITEROVER) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     300         245 :         if (result_int < 0) ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     301             : 
     302         245 :         std::shared_ptr<git::Branch> branch = std::make_shared<git::Branch>();
     303         245 :         const char *branch_name = nullptr;
     304         245 :         result_int = git_branch_name(&branch_name, ref.get());
     305         245 :         if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     306             : 
     307         245 :         std::string ref_name = git_reference_name(ref.get());
     308             : 
     309         245 :         branch->set_name(branch_name);
     310         245 :         branch->set_ref_name(ref_name);
     311             : 
     312         245 :         switch (git_branch_type)
     313             :         {
     314           0 :             case GIT_BRANCH_ALL: branch_type = git::BranchType::ALL; break;
     315         180 :             case GIT_BRANCH_LOCAL: branch_type = git::BranchType::LOCAL; break;
     316          65 :             case GIT_BRANCH_REMOTE: branch_type = git::BranchType::REMOTE; break;
     317           0 :             default: branch_type = git::BranchType::NOT_SET;
     318             :         }
     319             : 
     320         245 :         branch->set_type(branch_type);
     321             : 
     322         245 :         result_int = git_branch_is_head(ref.get());
     323         245 :         if (result_int == 1) branch->set_is_head(true);
     324             : 
     325         245 :         if (branch_type == git::BranchType::LOCAL)
     326             :         {
     327         180 :             git_buf buf_out = {nullptr};
     328         180 :             result_int = git_branch_upstream_remote(&buf_out, m_repo, ref_name.c_str());
     329         180 :             if (result_int == 0)
     330             :             {
     331         360 :                 std::string remote_name(buf_out.ptr, buf_out.ptr + buf_out.size);
     332         180 :                 branch->set_remote_name(remote_name);
     333         180 :                 git_buf_dispose(&buf_out);
     334         180 :             }
     335         180 :             result_int = git_branch_upstream_name(&buf_out, m_repo, ref_name.c_str());
     336         180 :             if (result_int == 0)
     337             :             {
     338         360 :                 std::string remote_branch(buf_out.ptr, buf_out.ptr + buf_out.size);
     339         180 :                 branch->set_remote_branch(remote_branch);
     340         180 :                 git_buf_dispose(&buf_out);
     341         180 :             }
     342             :         }
     343             :         else
     344          65 :             check_error(result, ""); //! \TODO why??
     345         735 :         branches.add(branch);
     346         653 :     }
     347             :     return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     348         163 : }
     349             : 
     350          15 : Result_t<bool> GitImpl::has_branch(const std::string &name, git::BranchType branch_type)
     351             : {
     352          15 :     Result_t<bool> result;
     353          15 :     ETRC_CALL_RET(result, name, branch_type);
     354             : 
     355          15 :     Guard<git_reference> ref;
     356          15 :     git_branch_t git_branch_type = GIT_BRANCH_ALL;
     357          15 :     Guard<git_annotated_commit> annotated_commit;
     358             :     // Guard<git_reference> branch_ref;
     359          15 :     Guard<git_reference> input_branch_ref;
     360          15 :     std::string new_ref = name;
     361             : 
     362          15 :     result = resolve_ref(input_branch_ref.get_p(), annotated_commit.get_p(), name);
     363          15 :     if (result.error())
     364             :     {
     365          15 :         self()->debug(0, "branch not found : " + name);
     366             :         // In the case where the content of branch doesn't have the full qualifier,
     367             :         // try to to guess
     368             : 
     369          15 :         result = find_ref(input_branch_ref.get_p(), annotated_commit.get_p(), name, new_ref);
     370          22 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT_T(result, false));
     371             :     }
     372             : 
     373          16 :     return ETRC_RET(ESYSREPO_RESULT_T(result, true));
     374          15 : }
     375             : 
     376          27 : Result GitImpl::get_hash(const std::string &revision, std::string &hash, git::BranchType branch_type)
     377             : {
     378          27 :     Result result;
     379          27 :     ETRC_CALL_RET_BEGIN(result, revision, hash, branch_type);
     380          27 :     ETRC_CALL_RET_OUT_END(hash);
     381             : 
     382          27 :     Guard<git_annotated_commit> annotated_commit;
     383          27 :     Guard<git_reference> revision_ref;
     384          27 :     std::string new_ref = revision;
     385             : 
     386          27 :     result = resolve_ref(revision_ref.get_p(), annotated_commit.get_p(), revision);
     387          27 :     if (result.error())
     388             :     {
     389           0 :         self()->debug(0, "revision not found : " + revision);
     390             :         // In the case where the content of branch doesn't have the full qualifier,
     391             :         // try to to guess
     392             : 
     393           0 :         result = find_ref(revision_ref.get_p(), annotated_commit.get_p(), revision, new_ref);
     394           0 :         if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     395             :     }
     396             : 
     397          27 :     const git_oid *oid = git_annotated_commit_id(annotated_commit.get());
     398          27 :     if (oid == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_GET_HASH_FAILED));
     399             : 
     400          27 :     result = convert_bin_hex(*oid, hash);
     401          54 :     return ETRC_RET(ESYSREPO_RESULT(result));
     402          27 : }
     403             : 
     404           7 : Result GitImpl::treeish_to_tree(Guard<git_tree> &tree, git_repository *repo, const char *treeish)
     405             : {
     406           7 :     Guard<git_object> obj;
     407             : 
     408           7 :     int result = git_revparse_single(obj.get_p(), repo, treeish);
     409           7 :     if (result < 0) return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
     410             : 
     411           7 :     result = git_object_peel((git_object **)tree.get_p(), obj.get(), GIT_OBJECT_TREE);
     412           7 :     if (result < 0) return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
     413           7 :     return ESYSREPO_RESULT(ResultCode::OK);
     414           7 : }
     415             : 
     416           2 : Result GitImpl::walk_commits(std::shared_ptr<git::WalkCommit> walk_commit)
     417             : {
     418           2 :     Result result;
     419           2 :     ETRC_CALL_RET_NP(result);
     420             : 
     421           2 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
     422           2 :     if (walk_commit == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
     423             : 
     424           2 :     git::CommitHash hash;
     425           2 :     Result rresult = get_last_commit(hash);
     426           2 :     if (rresult.error()) return ETRC_RET(ESYSREPO_RESULT(rresult));
     427             : 
     428           2 :     git_oid oid;
     429             : 
     430           2 :     rresult = convert_hex_bin(hash.get_hash(), oid);
     431           2 :     if (rresult.error()) return ETRC_RET(ESYSREPO_RESULT(rresult));
     432             : 
     433           2 :     Guard<git_revwalk> walker;
     434           2 :     Guard<git_commit> commit;
     435             : 
     436           2 :     git_revwalk_new(walker.get_p(), m_repo);
     437           2 :     git_revwalk_sorting(walker.get(), GIT_SORT_TOPOLOGICAL);
     438           2 :     git_revwalk_push(walker.get(), &oid);
     439             : 
     440             :     const char *commit_message;
     441             :     const git_signature *commit_signature;
     442             :     git_time_t commit_time;
     443          10 :     int result_int = 0;
     444             : 
     445          10 :     while (git_revwalk_next(&oid, walker.get()) == 0)
     446             :     {
     447           8 :         result_int = git_commit_lookup(commit.get_p(), m_repo, &oid);
     448           8 :         if (result_int != 0)
     449             :         {
     450           0 :             return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
     451             :         }
     452             : 
     453           8 :         commit_message = git_commit_message(commit.get());
     454           8 :         commit_signature = git_commit_committer(commit.get());
     455           8 :         commit_time = git_commit_time(commit.get());
     456             : 
     457           8 :         std::time_t commit_time_t = static_cast<std::time_t>(commit_time);
     458           8 :         std::string commit_time_str = std::asctime(std::localtime(&commit_time_t));
     459             : 
     460           8 :         auto commit_info = std::make_shared<git::Commit>();
     461             : 
     462           8 :         commit_info->set_message(commit_message);
     463           8 :         commit_info->set_author(commit_signature->name);
     464           8 :         commit_info->set_email(commit_signature->email);
     465           8 :         commit_info->set_date_time(std::chrono::system_clock::from_time_t(commit_time_t));
     466             : 
     467           8 :         rresult = convert_bin_hex(oid, commit_info->get_hash().get_hash());
     468           8 :         if (rresult.error()) return ETRC_RET(ESYSREPO_RESULT(rresult));
     469             : 
     470          16 :         walk_commit->new_commit(self(), commit_info);
     471          16 :         commit.reset();
     472           8 :     }
     473             : 
     474           4 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     475           4 : }
     476             : 
     477           4 : Result GitImpl::diff(const git::CommitHash &commit_hash, std::shared_ptr<git::Diff> diff)
     478             : {
     479           4 :     Result result;
     480           4 :     ETRC_CALL_RET_BEGIN(result, commit_hash, diff);
     481           4 :     ETRC_CALL_RET_OUT_END(diff);
     482             : 
     483           4 :     Guard<git_tree> commit_tree;
     484           4 :     Guard<git_tree> parent_tree;
     485           4 :     bool has_parent = true;
     486             : 
     487           4 :     result = treeish_to_tree(commit_tree, m_repo, commit_hash.get_hash().c_str());
     488           4 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     489             : 
     490           4 :     git::CommitHash parent_hash;
     491           4 :     result = get_parent_commit(commit_hash, parent_hash);
     492           4 :     if (result.error())
     493             :         has_parent = false;
     494             :     else
     495             :     {
     496           3 :         result = treeish_to_tree(parent_tree, m_repo, parent_hash.get_hash().c_str());
     497           3 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     498             :     }
     499             : 
     500           4 :     Guard<git_diff> the_diff;
     501           4 :     git_diff_options diffopts;
     502             : 
     503           4 :     int result_int = git_diff_options_init(&diffopts, GIT_DIFF_OPTIONS_VERSION);
     504           4 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
     505             : 
     506             :     // diffopts.id_abbrev = 40;
     507             : 
     508           4 :     if (has_parent)
     509           3 :         result_int = git_diff_tree_to_tree(the_diff.get_p(), m_repo, parent_tree.get(), commit_tree.get(), &diffopts);
     510             :     else
     511           1 :         result_int = git_diff_tree_to_tree(the_diff.get_p(), m_repo, nullptr, commit_tree.get(), &diffopts);
     512             : 
     513           4 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
     514             : 
     515           4 :     Guard<git_diff_stats> diff_stats;
     516             : 
     517           4 :     result_int = git_diff_get_stats(diff_stats.get_p(), the_diff.get());
     518           4 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
     519             : 
     520             :     /*diff_file_stats *filestats;
     521             : 
     522             :     size_t files_changed;
     523             :     size_t insertions;
     524             :     size_t deletions;
     525             :     size_t renames;
     526             : 
     527             :     size_t max_name;
     528             :     size_t max_filestat;
     529             :     int max_digits; */
     530           4 :     diff->set_files_changed(static_cast<unsigned int>(git_diff_stats_files_changed(diff_stats.get())));
     531           4 :     diff->set_insertions(static_cast<unsigned int>(git_diff_stats_insertions(diff_stats.get())));
     532           4 :     diff->set_deletions(static_cast<unsigned int>(git_diff_stats_deletions(diff_stats.get())));
     533           4 :     diff->set_renames(static_cast<unsigned int>(git_diff_stats_renames(diff_stats.get())));
     534           8 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     535          12 : }
     536             : 
     537           0 : Result GitImpl::get_ahead_behind(git::AheadBehind &ahead_behind, const git::Branch &local_branch)
     538             : {
     539           0 :     Result result;
     540           0 :     ETRC_CALL_RET_BEGIN(result, ahead_behind, local_branch);
     541           0 :     ETRC_CALL_RET_OUT_END(ahead_behind);
     542             : 
     543           0 :     size_t ahead = 0;
     544           0 :     size_t behind = 0;
     545           0 :     git_oid local;
     546           0 :     git_oid upstream;
     547             : 
     548           0 :     ahead_behind.set_ahead(0);
     549           0 :     ahead_behind.set_behind(0);
     550             : 
     551           0 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
     552           0 :     if (local_branch.get_remote_branch().empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
     553             : 
     554           0 :     result = get_commit_hash_from_branch(local_branch.get_name(), local);
     555           0 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     556             : 
     557           0 :     result = get_commit_hash_from_branch(local_branch.get_remote_branch(), upstream);
     558           0 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     559             : 
     560           0 :     int result_int = git_graph_ahead_behind(&ahead, &behind, m_repo, &local, &upstream);
     561           0 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR));
     562             : 
     563           0 :     ahead_behind.set_ahead(ahead);
     564           0 :     ahead_behind.set_behind(behind);
     565           0 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     566           0 : }
     567             : 
     568           3 : Result GitImpl::get_ahead_behind(git::AheadBehind &ahead_behind, const std::string &first_ref,
     569             :                                  const std::string &second_ref)
     570             : {
     571           3 :     Result result;
     572           3 :     ETRC_CALL_RET_BEGIN(result, ahead_behind, first_ref, second_ref);
     573           3 :     ETRC_CALL_RET_OUT_END(ahead_behind);
     574             : 
     575           3 :     size_t ahead = 0;
     576           3 :     size_t behind = 0;
     577           3 :     git_oid first_ref_oid;
     578           3 :     git_oid second_ref_oid;
     579             : 
     580           3 :     ahead_behind.set_ahead(0);
     581           3 :     ahead_behind.set_behind(0);
     582             : 
     583           3 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
     584             : 
     585           3 :     result = get_commit_hash_from_branch(first_ref, first_ref_oid);
     586           3 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     587             : 
     588           3 :     result = get_commit_hash_from_branch(second_ref, second_ref_oid);
     589           3 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     590             : 
     591           3 :     int result_int = git_graph_ahead_behind(&ahead, &behind, m_repo, &first_ref_oid, &second_ref_oid);
     592           3 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR));
     593             : 
     594           3 :     ahead_behind.set_ahead(ahead);
     595           3 :     ahead_behind.set_behind(behind);
     596           6 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     597           3 : }
     598             : 
     599         150 : Result GitImpl::clone(const std::string &url, const std::string &path, const std::string &rev,
     600             :                       const git::CloneOptions &options)
     601             : {
     602         150 :     Result rresult;
     603         150 :     ETRC_CALL_RET(rresult, url, path, rev);
     604             : 
     605         150 :     int result = 0;
     606             : 
     607         150 :     self()->debug(1, "[GitImpl::clone] begin ...");
     608             : 
     609         150 :     const bool https = (url.find("https:") == 0) || (url.find("http:") == 0);
     610         150 :     const bool ssh = is_ssh_url(url);
     611             : 
     612             :     // Payload must outlive git_clone when single_branch remote_cb is set.
     613         300 :     struct CloneRemotePayload
     614             :     {
     615             :         std::string rev;
     616         150 :     } remote_payload;
     617             : 
     618         300 :     auto make_opts = [&](git_clone_options &opts) {
     619         150 :         opts = GIT_CLONE_OPTIONS_INIT;
     620             :         // Default CloneOptions: ignore rev (legacy remote-default tip only).
     621         150 :         if (options.checkout_rev && !rev.empty()) opts.checkout_branch = rev.c_str();
     622         150 :         if (options.checkout_rev && options.single_branch && !rev.empty())
     623             :         {
     624           0 :             remote_payload.rev = rev;
     625           0 :             opts.remote_cb = [](git_remote **out, git_repository *repo, const char *name, const char *url,
     626             :                                 void *payload) -> int {
     627           0 :                 const auto *p = static_cast<const CloneRemotePayload *>(payload);
     628           0 :                 const char *remote_name = (name != nullptr && name[0] != '\0') ? name : "origin";
     629           0 :                 std::string branch_spec =
     630           0 :                     "+refs/heads/" + p->rev + ":refs/remotes/" + remote_name + "/" + p->rev;
     631           0 :                 int error = git_remote_create_with_fetchspec(out, repo, name, url, branch_spec.c_str());
     632           0 :                 if (error < 0) return error;
     633             :                 // Named tip may be a tag (clone --branch accepts both).
     634           0 :                 std::string tag_spec = "+refs/tags/" + p->rev + ":refs/tags/" + p->rev;
     635           0 :                 return git_remote_add_fetch(repo, name, tag_spec.c_str());
     636           0 :             };
     637           0 :             opts.remote_cb_payload = &remote_payload;
     638             :         }
     639         300 :     };
     640             : 
     641         150 :     if (ssh)
     642             :     {
     643         129 :         self()->debug(1, "[GitImpl::clone] ssh");
     644         129 :         rresult = ensure_ssh_backend();
     645         129 :         if (rresult.error()) return ETRC_RET(check_error(rresult, false));
     646             : 
     647         129 :         self()->cmd_start();
     648         129 :         self()->open_time();
     649         129 :         m_ssh_cred_attempt = 0;
     650         129 :         m_ssh_agent_tried = false;
     651             : 
     652         129 :         git_clone_options opts;
     653         129 :         make_opts(opts);
     654         129 :         setup_remote_callbacks(opts.fetch_opts.callbacks);
     655             : 
     656         129 :         result = git_clone(&m_repo, url.c_str(), path.c_str(), &opts);
     657             :     }
     658          21 :     else if (https)
     659             :     {
     660          17 :         self()->debug(1, "[GitImpl::clone] https");
     661          17 :         self()->cmd_start();
     662          17 :         self()->open_time();
     663             : 
     664          17 :         git_clone_options opts;
     665          17 :         make_opts(opts);
     666          17 :         result = git_clone(&m_repo, url.c_str(), path.c_str(), &opts);
     667             :     }
     668             :     else
     669             :     {
     670           4 :         self()->debug(1, "[GitImpl::clone] no protocol / local");
     671           4 :         self()->cmd_start();
     672           4 :         self()->open_time();
     673             : 
     674           4 :         git_clone_options opts;
     675           4 :         make_opts(opts);
     676           4 :         result = git_clone(&m_repo, url.c_str(), path.c_str(), &opts);
     677             :     }
     678             : 
     679         300 :     if (result == 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     680           0 :     rresult = ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result, "Clone failed");
     681           0 :     return ETRC_RET(check_error(rresult));
     682         150 : }
     683             : 
     684          74 : Result GitImpl::checkout(const std::string &branch, bool force)
     685             : {
     686          74 :     Result result;
     687          74 :     ETRC_CALL_RET(result, branch, force);
     688             : 
     689          74 :     Guard<git_annotated_commit> annotated_commit;
     690          74 :     Guard<git_object> treeish;
     691          74 :     Guard<git_commit> target_commit;
     692          74 :     Guard<git_reference> ref;
     693          74 :     Guard<git_reference> branch_ref;
     694          74 :     Guard<git_reference> input_branch_ref;
     695          74 :     std::string new_ref = branch;
     696             : 
     697          74 :     self()->cmd_start();
     698             : 
     699          74 :     result = resolve_ref(input_branch_ref.get_p(), annotated_commit.get_p(), branch);
     700          74 :     if (result.error())
     701             :     {
     702          45 :         self()->debug(0, "branch not found : " + branch);
     703             :         // In the case where the content of branch doesn't have the full qualifier,
     704             :         // try to to guess
     705             : 
     706          45 :         result = find_ref(input_branch_ref.get_p(), annotated_commit.get_p(), branch, new_ref);
     707          45 :         if (result.error())
     708             :         {
     709           0 :             return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     710             :         }
     711             :     }
     712             : 
     713          74 :     int result_int = git_commit_lookup(target_commit.get_p(), m_repo, git_annotated_commit_id(annotated_commit.get()));
     714          74 :     const git_oid *oid = git_commit_id(target_commit.get());
     715          74 :     std::string oid_str;
     716          74 :     result = convert_bin_hex(*oid, oid_str);
     717             : 
     718          74 :     git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
     719          74 :     if (force)
     720           0 :         opts.checkout_strategy = GIT_CHECKOUT_FORCE;
     721             :     else
     722             :         opts.checkout_strategy = GIT_CHECKOUT_SAFE;
     723             : 
     724          74 :     auto t_commit = static_cast<git_object *>(static_cast<void *>(target_commit.get()));
     725          74 :     result_int = git_checkout_tree(m_repo, t_commit, &opts);
     726          74 :     if (result_int < 0)
     727             :     {
     728           0 :         return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     729             :     }
     730             : 
     731          74 :     if (git_annotated_commit_ref(annotated_commit.get()))
     732             :     {
     733          58 :         const char *target_head = nullptr;
     734             : 
     735          58 :         result_int = git_reference_lookup(ref.get_p(), m_repo, git_annotated_commit_ref(annotated_commit.get()));
     736          58 :         if (result_int < 0)
     737             :         {
     738           0 :             return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     739             :         }
     740             : 
     741          58 :         if (git_reference_is_remote(ref.get()))
     742             :         {
     743          45 :             result_int =
     744          45 :                 git_branch_create_from_annotated(branch_ref.get_p(), m_repo, branch.c_str(), annotated_commit.get(), 0);
     745          45 :             if (result_int < 0)
     746             :             {
     747           0 :                 return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     748             :             }
     749             : 
     750          45 :             target_head = git_reference_name(branch_ref.get());
     751             : 
     752             :             // if (git_reference_is_branch(input_branch_ref.get()))
     753             :             // git_
     754          45 :             const char *branch_name = nullptr;
     755          45 :             result_int = git_branch_name(&branch_name, input_branch_ref.get());
     756          45 :             if (result_int < 0)
     757           0 :                 check_error(result, "can't get the name of the branch"); //!  \TODO check if this is correct
     758             :             else
     759             :             {
     760          45 :                 result_int = git_branch_set_upstream(branch_ref.get(), branch_name);
     761          45 :                 if (result_int < 0) check_error(result, "set branch upstream"); //!  \TODO check if this is correct
     762             :             }
     763             :         }
     764             :         else
     765             :         {
     766          13 :             target_head = git_annotated_commit_ref(annotated_commit.get());
     767             :         }
     768             : 
     769          58 :         result_int = git_repository_set_head(m_repo, target_head);
     770          58 :         Result rresult;
     771             : 
     772          58 :         if (result_int < 0)
     773           0 :             rresult = ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int);
     774             :         else
     775          58 :             rresult = ESYSREPO_RESULT(ResultCode::OK);
     776         116 :         return ETRC_RET(check_error(rresult));
     777          58 :     }
     778             :     else
     779             :     {
     780          16 :         result_int = git_repository_set_head_detached_from_annotated(m_repo, annotated_commit.get());
     781          16 :         Result rresult;
     782          16 :         if (result_int < 0)
     783           0 :             rresult = ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int);
     784             :         else
     785          16 :             rresult = ESYSREPO_RESULT(ResultCode::OK);
     786             : 
     787          32 :         return ETRC_RET(check_error(rresult));
     788          16 :     }
     789          74 : }
     790             : 
     791           2 : Result GitImpl::reset(const git::CommitHash &commit, git::ResetType type)
     792             : {
     793           2 :     Result result;
     794           2 :     ETRC_CALL_RET(result, commit, type);
     795             : 
     796           2 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
     797             : 
     798           2 :     git_reset_t reset_type = GIT_RESET_SOFT;
     799             : 
     800           2 :     switch (type)
     801             :     {
     802           0 :         case git::ResetType::NOT_SET: return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RESET_TYPE_NOT_SET));
     803             :         case git::ResetType::HARD: reset_type = GIT_RESET_HARD; break;
     804           0 :         case git::ResetType::MIXED: reset_type = GIT_RESET_MIXED; break;
     805           0 :         case git::ResetType::SOFT: reset_type = GIT_RESET_SOFT; break;
     806           0 :         default: return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RESET_TYPE_UNKNOWN));
     807             :     }
     808             : 
     809           2 :     Guard<git_commit> g_commit;
     810           2 :     git_oid oid_commit;
     811             : 
     812           2 :     result = convert_hex_bin(commit.get_hash(), oid_commit);
     813           2 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     814             : 
     815             :     // get the actual commit structure
     816           2 :     int result_int = git_commit_lookup(g_commit.get_p(), m_repo, &oid_commit);
     817           2 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     818             : 
     819           2 :     auto t_commit = static_cast<git_object *>(static_cast<void *>(g_commit.get()));
     820           2 :     result_int = git_reset(m_repo, t_commit, reset_type, nullptr);
     821           4 :     if (result_int == GIT_OK) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     822           0 :     return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     823           2 : }
     824             : 
     825           2 : Result GitImpl::fastforward(const git::CommitHash &commit)
     826             : {
     827           2 :     Result result;
     828           2 :     ETRC_CALL_RET(result, commit);
     829             : 
     830           2 :     git_checkout_options ff_checkout_options = GIT_CHECKOUT_OPTIONS_INIT;
     831           2 :     Guard<git_reference> target_ref;
     832           2 :     Guard<git_reference> new_target_ref;
     833           2 :     Guard<git_object> target;
     834           2 :     git_oid target_oid;
     835             : 
     836           2 :     assert(m_repo != nullptr);
     837             : 
     838           2 :     result = convert_hex_bin(commit.get_hash(), target_oid);
     839           2 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     840             : 
     841             :     // HEAD exists, just lookup and resolve
     842           2 :     int result_int = git_repository_head(target_ref.get_p(), m_repo);
     843           2 :     if (result_int != GIT_OK)
     844           0 :         return ETRC_RET(
     845             :             check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "failed to get HEAD reference")));
     846             : 
     847             :     // Lookup the target object
     848           2 :     result_int = git_object_lookup(target.get_p(), m_repo, &target_oid, GIT_OBJECT_COMMIT);
     849           2 :     if (result_int != GIT_OK)
     850           0 :         return ETRC_RET(check_error(
     851             :             ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "failed to lookup OID " + commit.get_hash())));
     852             : 
     853             :     // Checkout the result so the workdir is in the expected state
     854           2 :     ff_checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE;
     855           2 :     result_int = git_checkout_tree(m_repo, target.get(), &ff_checkout_options);
     856           2 :     if (result_int != 0)
     857           0 :         return ETRC_RET(check_error(
     858             :             ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "failed to checkout HEAD reference")));
     859             : 
     860             :     // Move the target reference to the target OID
     861           2 :     result_int = git_reference_set_target(new_target_ref.get_p(), target_ref.get(), &target_oid, nullptr);
     862           2 :     if (result_int != 0)
     863           0 :         return ETRC_RET(
     864             :             check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "failed to move HEAD reference")));
     865             : 
     866           4 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     867           2 : }
     868             : 
     869          47 : Result GitImpl::get_commit_hash(const git_oid &oid_commit, git::CommitHash &commit_hash)
     870             : {
     871          47 :     Result result;
     872          47 :     ETRC_CALL_RET_BEGIN(result, commit_hash);
     873          47 :     ETRC_CALL_RET_OUT_END(commit_hash);
     874             : 
     875          47 :     std::string hash;
     876             : 
     877          47 :     result = convert_bin_hex(oid_commit, hash);
     878          47 :     if (result.ok())
     879             :     {
     880          47 :         commit_hash.set_hash(hash);
     881          94 :         return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     882             :     }
     883             : 
     884           0 :     return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     885          47 : }
     886             : 
     887           7 : Result GitImpl::get_commit(const git_oid &oid_commit, git::Commit &commit, bool get_all_notes)
     888             : {
     889           7 :     Result result;
     890           7 :     ETRC_CALL_RET_BEGIN(result, commit, get_all_notes);
     891           7 :     ETRC_CALL_RET_OUT_END(commit);
     892             : 
     893           7 :     Guard<git_commit> g_commit;
     894             : 
     895             :     // get the actual commit structure
     896           7 :     auto result_int = git_commit_lookup(g_commit.get_p(), m_repo, &oid_commit);
     897           7 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     898             : 
     899           7 :     result = get_commit_hash(oid_commit, commit.get_hash());
     900           7 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     901             : 
     902           7 :     auto message = git_commit_message(g_commit.get());
     903          14 :     if (message != nullptr) commit.set_message(message);
     904             : 
     905           7 :     auto summary = git_commit_summary(g_commit.get());
     906          14 :     if (summary != nullptr) commit.set_summary(summary);
     907             : 
     908           7 :     auto body = git_commit_body(g_commit.get());
     909          12 :     if (body != nullptr) commit.set_body(body);
     910             : 
     911           7 :     auto author = git_commit_author(g_commit.get());
     912           7 :     result = get_signature(author, commit.get_author_sign());
     913           7 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     914             : 
     915           7 :     auto committer = git_commit_committer(g_commit.get());
     916           7 :     result = get_signature(committer, commit.get_committer_sign());
     917           7 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     918             : 
     919           8 :     if (!get_all_notes) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     920             : 
     921           6 :     result = m_notes.load_all();
     922           6 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     923             : 
     924           6 :     std::vector<std::shared_ptr<git::Note>> notes;
     925             : 
     926           6 :     result = m_notes.get_note(oid_commit, notes);
     927           6 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     928             : 
     929          11 :     for (auto note : notes)
     930             :     {
     931          15 :         commit.add_note(note);
     932           5 :     }
     933             : 
     934          12 :     return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     935          13 : }
     936             : 
     937          32 : Result GitImpl::get_last_commit(git::CommitHash &commit_hash)
     938             : {
     939          32 :     Result result;
     940          32 :     ETRC_CALL_RET_BEGIN(result, commit_hash);
     941          32 :     ETRC_CALL_RET_OUT_END(commit_hash);
     942             : 
     943          32 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
     944             : 
     945          32 :     git_oid oid_last_commit;
     946             : 
     947          32 :     self()->cmd_start();
     948             : 
     949             :     // resolve HEAD into a SHA1
     950          32 :     auto result_int = git_reference_name_to_id(&oid_last_commit, m_repo, "HEAD");
     951          32 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     952             : 
     953          32 :     result = get_commit_hash(oid_last_commit, commit_hash);
     954          64 :     return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     955          32 : }
     956             : 
     957           7 : Result GitImpl::get_last_commit(git::Commit &commit, bool get_all_notes)
     958             : {
     959           7 :     Result result;
     960           7 :     ETRC_CALL_RET_BEGIN(result, commit, get_all_notes);
     961           7 :     ETRC_CALL_RET_OUT_END(commit);
     962             : 
     963           7 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
     964             : 
     965           7 :     git_oid oid_last_commit;
     966             : 
     967           7 :     self()->cmd_start();
     968             : 
     969           7 :     commit.clear();
     970             : 
     971             :     // resolve HEAD into a SHA1
     972           7 :     auto result_int = git_reference_name_to_id(&oid_last_commit, m_repo, "HEAD");
     973           7 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     974             : 
     975           7 :     result = get_commit(oid_last_commit, commit, get_all_notes);
     976          14 :     return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     977           7 : }
     978             : 
     979           0 : Result GitImpl::get_notes(git_commit *commit_obj, git::Commit &commit, const std::string &notes_ref)
     980             : {
     981           0 :     Guard<git_iterator> it;
     982             : 
     983           0 :     auto result_int = git_note_iterator_new(it.get_p(), m_repo, notes_ref.c_str());
     984             : 
     985           0 :     return check_error(ESYSREPO_RESULT(ResultCode::OK));
     986           0 : }
     987             : 
     988          24 : Result GitImpl::get_signature(const git_signature *signature_obj, git::Signature &signature)
     989             : {
     990          24 :     signature.set_name(signature_obj->name);
     991          24 :     signature.set_email(signature_obj->email);
     992             : 
     993          24 :     git_time_t commit_time = signature_obj->when.time;
     994             : 
     995          24 :     std::time_t commit_time_t = static_cast<std::time_t>(commit_time);
     996          24 :     std::string commit_time_str = std::asctime(std::localtime(&commit_time_t));
     997             : 
     998          24 :     signature.set_date_time(std::chrono::system_clock::from_time_t(commit_time_t));
     999          24 :     signature.set_offset(signature_obj->when.offset);
    1000             : 
    1001          24 :     return ESYSREPO_RESULT(ResultCode::OK);
    1002          24 : }
    1003             : 
    1004           9 : Result GitImpl::get_parent_commit(const git::CommitHash &commit, git::CommitHash &parent, int nth_parent)
    1005             : {
    1006           9 :     Result result;
    1007           9 :     ETRC_CALL_RET_BEGIN(result, commit, parent, nth_parent);
    1008           9 :     ETRC_CALL_RET_OUT_END(parent);
    1009             : 
    1010           9 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    1011             : 
    1012           9 :     Guard<git_commit> g_commit;
    1013           9 :     Guard<git_commit> cur_commit;
    1014           9 :     Guard<git_commit> parent_commit;
    1015           9 :     git_oid oid_commit;
    1016             : 
    1017           9 :     self()->cmd_start();
    1018             : 
    1019           9 :     if (nth_parent == 0)
    1020             :     {
    1021           0 :         parent = commit;
    1022           0 :         return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
    1023             :     }
    1024             : 
    1025           9 :     result = convert_hex_bin(commit.get_hash(), oid_commit);
    1026           9 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
    1027             : 
    1028             :     // get the actual commit structure
    1029           9 :     int result_int = git_commit_lookup(cur_commit.get_p(), m_repo, &oid_commit);
    1030           9 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
    1031             : 
    1032          20 :     unsigned int count = 0;
    1033             : 
    1034          20 :     for (int idx = 0; idx < nth_parent; ++idx)
    1035             :     {
    1036          12 :         count = git_commit_parentcount(cur_commit.get());
    1037          12 :         if (count <= 0)
    1038             :         {
    1039           2 :             return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_NO_PARENT)));
    1040             :         }
    1041             : 
    1042          11 :         parent_commit.reset();
    1043          11 :         result_int = git_commit_parent(parent_commit.get_p(), cur_commit.get(), 0);
    1044          11 :         if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
    1045             : 
    1046          22 :         cur_commit = parent_commit;
    1047             :     }
    1048             : 
    1049           8 :     const git_oid *parent_commit_id = git_commit_id(parent_commit.get());
    1050           8 :     if (parent_commit_id == nullptr) check_error(-3);
    1051             : 
    1052           8 :     std::string hash;
    1053           8 :     result = convert_bin_hex(*parent_commit_id, hash);
    1054           8 :     if (result.ok())
    1055             :     {
    1056           8 :         parent.set_hash(hash);
    1057          16 :         return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    1058             :     }
    1059             : 
    1060           0 :     return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
    1061          18 : }
    1062             : 
    1063          30 : Result GitImpl::is_dirty(bool &dirty)
    1064             : {
    1065          30 :     Result result;
    1066          30 :     ETRC_CALL_RET_BEGIN(result, dirty);
    1067          30 :     ETRC_CALL_RET_OUT_END(dirty);
    1068             : 
    1069          30 :     if (m_repo == nullptr) return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
    1070             : 
    1071          30 :     Guard<git_status_list> status;
    1072          30 :     git_status_options statusopt = GIT_STATUS_OPTIONS_INIT;
    1073             : 
    1074          30 :     self()->cmd_start();
    1075             : 
    1076          30 :     statusopt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
    1077          30 :     statusopt.flags = GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
    1078             : 
    1079          30 :     int result_int = git_status_list_new(status.get_p(), m_repo, &statusopt);
    1080          30 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
    1081             : 
    1082          30 :     std::size_t count = git_status_list_entrycount(status.get());
    1083          30 :     dirty = (count != 0);
    1084          60 :     return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
    1085          30 : }
    1086             : 
    1087          26 : Result GitImpl::is_detached(bool &detached)
    1088             : {
    1089          26 :     Result result;
    1090          26 :     ETRC_CALL_RET_BEGIN(result, detached);
    1091          26 :     ETRC_CALL_RET_OUT_END(detached);
    1092             : 
    1093          26 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    1094             : 
    1095          26 :     int result_int = git_repository_head_detached(m_repo);
    1096          26 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    1097             : 
    1098          26 :     if (result_int == 1)
    1099           3 :         detached = true;
    1100             :     else
    1101          23 :         detached = false;
    1102          52 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    1103          26 : }
    1104             : 
    1105          31 : Result GitImpl::get_status(git::RepoStatus &repo_status)
    1106             : {
    1107          31 :     Result result;
    1108          31 :     ETRC_CALL_RET_BEGIN(result, repo_status);
    1109          31 :     ETRC_CALL_RET_OUT_END(repo_status);
    1110             : 
    1111          31 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    1112             : 
    1113          31 :     const git_status_entry *status_entry = nullptr;
    1114          31 :     Guard<git_status_list> status_list;
    1115          31 :     git_status_options statusopt = GIT_STATUS_OPTIONS_INIT;
    1116             : 
    1117          31 :     self()->cmd_start();
    1118             : 
    1119          31 :     statusopt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
    1120          31 :     statusopt.flags =
    1121             :         GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
    1122             : 
    1123          31 :     int result_int = git_status_list_new(status_list.get_p(), m_repo, &statusopt);
    1124          31 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
    1125             : 
    1126          31 :     std::size_t count = git_status_list_entrycount(status_list.get());
    1127             : 
    1128          37 :     for (std::size_t idx = 0; idx < count; ++idx)
    1129             :     {
    1130           6 :         status_entry = git_status_byindex(status_list.get(), idx);
    1131             : 
    1132           6 :         handle_status_entry(repo_status, status_entry);
    1133             :     }
    1134          62 :     return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
    1135          31 : }
    1136             : 
    1137           6 : Result GitImpl::handle_status_entry(git::RepoStatus &repo_status, const git_status_entry *status_entry)
    1138             : {
    1139           6 :     std::shared_ptr<git::Status> status = std::make_shared<git::Status>();
    1140           6 :     Result result;
    1141             : 
    1142           6 :     if (status_entry->status == GIT_STATUS_CURRENT)
    1143           0 :         result = handle_status_entry_current(repo_status, status, status_entry);
    1144           6 :     else if (status_entry->status < GIT_STATUS_WT_NEW)
    1145           0 :         result = handle_status_entry_index(repo_status, status, status_entry);
    1146           6 :     else if (status_entry->status < GIT_STATUS_IGNORED)
    1147          18 :         result = handle_status_entry_work_dir(repo_status, status, status_entry);
    1148           0 :     else if (status_entry->status == GIT_STATUS_IGNORED)
    1149           0 :         result = handle_status_entry_ignored(repo_status, status, status_entry);
    1150           0 :     else if (status_entry->status == GIT_STATUS_CONFLICTED)
    1151           0 :         result = handle_status_entry_conflicted(repo_status, status, status_entry);
    1152             :     else
    1153           0 :         return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
    1154             : 
    1155          12 :     repo_status.add(status);
    1156          12 :     return ESYSREPO_RESULT(result); // It was 0?!
    1157          12 : }
    1158             : 
    1159           0 : Result GitImpl::handle_status_entry_current(git::RepoStatus &repo_status, std::shared_ptr<git::Status> status,
    1160             :                                             const git_status_entry *status_entry)
    1161             : {
    1162           0 :     status->set_type(git::StatusType::CURRENT);
    1163             : 
    1164           0 :     return ESYSREPO_RESULT(ResultCode::OK);
    1165             : }
    1166             : 
    1167           0 : Result GitImpl::handle_status_entry_index(git::RepoStatus &repo_status, std::shared_ptr<git::Status> status,
    1168             :                                           const git_status_entry *status_entry)
    1169             : {
    1170           0 :     status->set_type(git::StatusType::INDEX);
    1171             : 
    1172           0 :     return ESYSREPO_RESULT(ResultCode::OK);
    1173             : }
    1174             : 
    1175           6 : Result GitImpl::handle_status_entry_work_dir(git::RepoStatus &repo_status, std::shared_ptr<git::Status> status,
    1176             :                                              const git_status_entry *status_entry)
    1177             : {
    1178           6 :     status->set_type(git::StatusType::WORKING_DIR);
    1179             : 
    1180           6 :     Result result = from_to(status_entry->index_to_workdir, status->get_diff_delta());
    1181           6 :     if (result.error()) return ESYSREPO_RESULT(result);
    1182             : 
    1183          12 :     result = from_to(status_entry->status, status);
    1184           6 :     if (result.error()) return ESYSREPO_RESULT(result);
    1185             : 
    1186           6 :     return ESYSREPO_RESULT(ResultCode::OK);
    1187           6 : }
    1188             : 
    1189           0 : Result GitImpl::handle_status_entry_conflicted(git::RepoStatus &repo_status, std::shared_ptr<git::Status> status,
    1190             :                                                const git_status_entry *status_entry)
    1191             : {
    1192           0 :     status->set_type(git::StatusType::CONFLICTED);
    1193             : 
    1194           0 :     return ESYSREPO_RESULT(ResultCode::OK);
    1195             : }
    1196             : 
    1197           0 : Result GitImpl::handle_status_entry_ignored(git::RepoStatus &repo_status, std::shared_ptr<git::Status> status,
    1198             :                                             const git_status_entry *status_entry)
    1199             : {
    1200           0 :     status->set_type(git::StatusType::IGNORED);
    1201             : 
    1202           0 :     return ESYSREPO_RESULT(ResultCode::OK);
    1203             : }
    1204             : 
    1205           6 : Result GitImpl::from_to(git_status_t status, std::shared_ptr<git::Status> status_ptr)
    1206             : {
    1207           6 :     switch (status)
    1208             :     {
    1209           5 :         case GIT_STATUS_INDEX_NEW:
    1210           5 :         case GIT_STATUS_WT_NEW: status_ptr->set_sub_type(git::StatusSubType::NEW); break;
    1211             : 
    1212           1 :         case GIT_STATUS_INDEX_MODIFIED:
    1213           1 :         case GIT_STATUS_WT_MODIFIED: status_ptr->set_sub_type(git::StatusSubType::MODIFIED); break;
    1214             : 
    1215           0 :         case GIT_STATUS_INDEX_DELETED:
    1216           0 :         case GIT_STATUS_WT_DELETED: status_ptr->set_sub_type(git::StatusSubType::DELETED); break;
    1217             : 
    1218           0 :         case GIT_STATUS_INDEX_RENAMED:
    1219           0 :         case GIT_STATUS_WT_RENAMED: status_ptr->set_sub_type(git::StatusSubType::RENAMED); break;
    1220             : 
    1221           0 :         case GIT_STATUS_INDEX_TYPECHANGE:
    1222           0 :         case GIT_STATUS_WT_TYPECHANGE: status_ptr->set_sub_type(git::StatusSubType::TYPECHANGE); break;
    1223             : 
    1224           0 :         case GIT_STATUS_WT_UNREADABLE: status_ptr->set_sub_type(git::StatusSubType::UNREADABLE); break;
    1225             : 
    1226           0 :         default: return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
    1227             :     }
    1228           6 :     return ESYSREPO_RESULT(ResultCode::OK);
    1229             : }
    1230             : 
    1231           6 : Result GitImpl::from_to(const git_diff_delta *delta, git::DiffDelta &diff_delta)
    1232             : {
    1233           6 :     diff_delta.set_file_count(delta->nfiles);
    1234           6 :     diff_delta.set_similatiry(delta->similarity);
    1235             : 
    1236           6 :     Result result = from_to(delta->old_file, diff_delta.get_old_file());
    1237           6 :     if (result.error()) return ESYSREPO_RESULT(result);
    1238             : 
    1239           6 :     result = from_to(delta->new_file, diff_delta.get_new_file());
    1240           6 :     if (result.error()) return ESYSREPO_RESULT(result);
    1241             : 
    1242           6 :     return ESYSREPO_RESULT(ResultCode::OK);
    1243           6 : }
    1244             : 
    1245          12 : Result GitImpl::from_to(const git_diff_file &file, git::DiffFile &diff_file)
    1246             : {
    1247          12 :     std::string id;
    1248          12 :     Result result = convert_bin_hex(file.id, id);
    1249          12 :     if (result.ok()) diff_file.set_id(id);
    1250             : 
    1251          12 :     diff_file.set_path(file.path);
    1252          12 :     diff_file.set_size(file.size);
    1253             : 
    1254          12 :     git::FileMode mode = git::FileMode::NOT_SET;
    1255             : 
    1256          12 :     switch (file.mode)
    1257             :     {
    1258             :         case GIT_FILEMODE_UNREADABLE: mode = git ::FileMode::NEW; break;
    1259           3 :         case GIT_FILEMODE_TREE: mode = git ::FileMode::TREE; break;
    1260           4 :         case GIT_FILEMODE_BLOB: mode = git ::FileMode::BLOB; break;
    1261           0 :         case GIT_FILEMODE_BLOB_EXECUTABLE: mode = git ::FileMode::BLOB_EXECUTABLE; break;
    1262           0 :         case GIT_FILEMODE_LINK: mode = git ::FileMode::LINK; break;
    1263           0 :         case GIT_FILEMODE_COMMIT: mode = git ::FileMode::COMMIT; break;
    1264           0 :         default: mode = git::FileMode::NOT_SET;
    1265             :     }
    1266             : 
    1267          12 :     diff_file.set_mode(mode);
    1268             : 
    1269          12 :     return result;
    1270          12 : }
    1271             : 
    1272           0 : int GitImpl::check_error(int result, const std::string &action, bool show_result)
    1273             : {
    1274           0 :     self()->cmd_end();
    1275             : 
    1276           0 :     if (result == 0) return 0;
    1277             : 
    1278           0 :     const git_error *error = git_error_last();
    1279             : 
    1280           0 :     std::ostringstream oss;
    1281             :     // oss << "ERROR " << result << " : " << action;
    1282           0 :     oss << action;
    1283           0 :     if (show_result)
    1284             :     {
    1285           0 :         oss << " (" << result << ").";
    1286           0 :         if (error && error->message)
    1287             :         {
    1288           0 :             oss << " - " << error->message;
    1289             :         }
    1290             :     }
    1291           0 :     self()->error(oss.str());
    1292             : 
    1293           0 :     return result;
    1294           0 : }
    1295             : 
    1296         679 : Result GitImpl::check_error(Result result, bool show_result)
    1297             : {
    1298         679 :     self()->cmd_end();
    1299             : 
    1300         679 :     if (result.ok()) return result;
    1301             : 
    1302           1 :     const git_error *error = git_error_last();
    1303             : 
    1304           1 :     std::ostringstream oss;
    1305             :     // oss << "ERROR " << result << " : " << action;
    1306           3 :     if (result.get_error_info() != nullptr) oss << result.get_error_info()->get_text();
    1307           1 :     if (show_result)
    1308             :     {
    1309           1 :         oss << " (" << result.get_result_code_int() << ").";
    1310           1 :         if (error && error->message)
    1311             :         {
    1312           1 :             oss << " - " << error->message;
    1313             :         }
    1314             :     }
    1315           1 :     self()->error(oss.str());
    1316             : 
    1317           1 :     return result;
    1318           1 : }
    1319             : 
    1320      111552 : Git *GitImpl::self() const
    1321             : {
    1322      111552 :     return m_self;
    1323             : }
    1324             : 
    1325         192 : int GitImpl::libgit2_credentials_cb(git_credential **out, const char *url, const char *name, unsigned int types,
    1326             :                                     void *payload)
    1327             : {
    1328         192 :     (void)url;
    1329             : 
    1330         192 :     const git_error *error = git_error_last();
    1331         192 :     if (error && error->klass == GIT_ERROR_SSH) return -1;
    1332             : 
    1333         192 :     auto self = static_cast<GitImpl *>(payload);
    1334         192 :     self->m_ssh_cred_attempt++;
    1335             : 
    1336         192 :     const char *username = (name != nullptr && name[0] != '\0') ? name : "git";
    1337             : 
    1338         192 :     Result_t<git::SshBackend> backend = GitBase::get_ssh_backend();
    1339         192 :     const bool use_exec = backend.ok() && backend.get() == git::SshBackend::Exec;
    1340             : 
    1341             :     // OpenSSH exec: the external ssh client owns key/agent auth. Only supply username.
    1342         192 :     if (use_exec)
    1343             :     {
    1344           0 :         if (types & GIT_CREDENTIAL_USERNAME) return git_credential_username_new(out, username);
    1345           0 :         if (types & GIT_CREDENTIAL_DEFAULT) return git_credential_default_new(out);
    1346           0 :         self->self()->debug(0, "[GitImpl::libgit2_credentials_cb] exec backend; no further credentials needed");
    1347           0 :         return 1; // no credential acquired
    1348             :     }
    1349             : 
    1350             :     // libssh2: prefer agent when present, else default identity files (agent not required).
    1351         192 :     if ((types & GIT_CREDENTIAL_SSH_KEY) || (types & GIT_CREDENTIAL_SSH_MEMORY))
    1352             :     {
    1353         384 :         if (!self->m_ssh_agent_tried && self->is_ssh_agent_running())
    1354             :         {
    1355         192 :             self->m_ssh_agent_tried = true;
    1356         192 :             self->self()->debug(0, "[GitImpl::libgit2_credentials_cb] using ssh agent");
    1357             : 
    1358         192 :             std::string agent_id_path = libssh2::SSH::get_dflt_agent_identity_path();
    1359         192 :             if (agent_id_path.empty())
    1360             :             {
    1361           0 :                 log_ssh_auth_info_once(self->self(), "SSH credentials: libssh2 via ssh-agent");
    1362           0 :                 return git_credential_ssh_key_from_agent(out, username);
    1363             :             }
    1364             : 
    1365         192 :             std::ostringstream oss;
    1366         192 :             oss << "[GitImpl::libgit2_credentials_cb] use custom agent with path '" << agent_id_path << "'";
    1367         192 :             self->self()->debug(0, oss.str());
    1368         192 :             log_ssh_auth_info_once(self->self(),
    1369         384 :                                    "SSH credentials: libssh2 via custom ssh-agent ('" + agent_id_path + "')");
    1370         192 :             return git_credential_ssh_key_from_custom_agent(out, username, agent_id_path.c_str());
    1371         384 :         }
    1372             : 
    1373           0 :         const int key_index = self->m_ssh_cred_attempt - (self->m_ssh_agent_tried ? 2 : 1);
    1374           0 :         self->self()->debug(0, "[GitImpl::libgit2_credentials_cb] trying default ssh identity files");
    1375           0 :         std::string key_path;
    1376           0 :         int key_result = try_default_ssh_key(out, username, key_index, &key_path);
    1377           0 :         if (key_result == 0)
    1378             :         {
    1379           0 :             log_ssh_auth_info_once(self->self(), "SSH credentials: libssh2 identity file '" + key_path + "'");
    1380           0 :             return 0;
    1381             :         }
    1382         192 :     }
    1383             : 
    1384           0 :     if (types & GIT_CREDENTIAL_USERNAME) return git_credential_username_new(out, username);
    1385             : 
    1386           0 :     self->self()->debug(0, "[GitImpl::libgit2_credentials_cb] no usable credentials");
    1387           0 :     return -1;
    1388         192 : }
    1389             : 
    1390         150 : bool GitImpl::is_ssh_url(const std::string &url)
    1391             : {
    1392         150 :     if (url.rfind("ssh://", 0) == 0) return true;
    1393          21 :     if (url.rfind("ssh:", 0) == 0) return true;
    1394             : 
    1395             :     // scp-like: git@host:path (no ://)
    1396          21 :     if (url.find("://") != std::string::npos) return false;
    1397           4 :     const auto at = url.find('@');
    1398           4 :     const auto colon = url.find(':');
    1399           4 :     return at != std::string::npos && colon != std::string::npos && colon > at;
    1400             : }
    1401             : 
    1402         202 : void GitImpl::setup_remote_callbacks(git_remote_callbacks &callbacks)
    1403             : {
    1404         202 :     callbacks.sideband_progress = &GitImpl::libgit2_sideband_progress_cb;
    1405         202 :     callbacks.credentials = &GitImpl::libgit2_credentials_cb;
    1406         202 :     callbacks.transfer_progress = &GitImpl::libgit2_transfer_progress_cb;
    1407         202 :     callbacks.update_tips = &GitImpl::libgit2_update_tips_cb;
    1408         202 :     callbacks.pack_progress = &GitImpl::libgit2_pack_progress_cb;
    1409         202 :     callbacks.payload = this;
    1410         202 : }
    1411             : 
    1412         202 : Result GitImpl::ensure_ssh_backend()
    1413             : {
    1414         202 :     static std::mutex mutex;
    1415         202 :     static bool done = false;
    1416         202 :     static Result cached = Result::OK;
    1417             : 
    1418         202 :     std::lock_guard<std::mutex> lock(mutex);
    1419         202 :     if (done) return cached;
    1420             : 
    1421           1 :     done = true;
    1422             : 
    1423             :     // Optional override: ESYSREPO_SSH_BACKEND=libssh2|exec (CI unit tests force
    1424             :     // libssh2 so the GitLab ssh-agent deploy key works; OpenSSH exec is the
    1425             :     // default preference on developer machines when available).
    1426           1 :     const char *override_env = std::getenv("ESYSREPO_SSH_BACKEND");
    1427           1 :     if (override_env != nullptr)
    1428             :     {
    1429           1 :         const std::string override_name = override_env;
    1430           1 :         if (override_name == "libssh2")
    1431             :         {
    1432           1 :             if (GitBase::is_ssh_backend_supported(git::SshBackend::LibSSH2))
    1433             :             {
    1434           1 :                 cached = GitBase::set_ssh_backend(git::SshBackend::LibSSH2);
    1435           1 :                 if (cached.ok())
    1436             :                 {
    1437           1 :                     self()->info("SSH backend: libssh2 (ESYSREPO_SSH_BACKEND)");
    1438           1 :                     return cached;
    1439             :                 }
    1440             :             }
    1441           0 :             self()->warn("ESYSREPO_SSH_BACKEND=libssh2 requested but unavailable");
    1442             :         }
    1443           0 :         else if (override_name == "exec")
    1444             :         {
    1445           0 :             if (GitBase::is_ssh_backend_available(git::SshBackend::Exec))
    1446             :             {
    1447           0 :                 cached = GitBase::set_ssh_backend(git::SshBackend::Exec);
    1448           0 :                 if (cached.ok())
    1449             :                 {
    1450           0 :                     self()->info("SSH backend: OpenSSH (exec) (ESYSREPO_SSH_BACKEND)");
    1451           0 :                     return cached;
    1452             :                 }
    1453             :             }
    1454           0 :             self()->warn("ESYSREPO_SSH_BACKEND=exec requested but unavailable");
    1455             :         }
    1456           0 :         else if (!override_name.empty())
    1457             :         {
    1458           0 :             self()->warn("Unknown ESYSREPO_SSH_BACKEND='" + override_name
    1459           0 :                          + "' (expected libssh2 or exec); using auto selection");
    1460             :         }
    1461           1 :     }
    1462             : 
    1463           0 :     if (GitBase::is_ssh_backend_available(git::SshBackend::Exec))
    1464             :     {
    1465           0 :         cached = GitBase::set_ssh_backend(git::SshBackend::Exec);
    1466           0 :         if (cached.ok())
    1467             :         {
    1468           0 :             self()->info("SSH backend: OpenSSH (exec); credentials via ssh "
    1469             :                          "(agent/keys as configured, e.g. omniSSHAgent / ssh-agent)");
    1470           0 :             return cached;
    1471             :         }
    1472           0 :         self()->warn("Failed to select OpenSSH exec backend; trying libssh2");
    1473             :     }
    1474             : 
    1475           0 :     if (GitBase::is_ssh_backend_supported(git::SshBackend::LibSSH2))
    1476             :     {
    1477           0 :         cached = GitBase::set_ssh_backend(git::SshBackend::LibSSH2);
    1478           0 :         if (cached.ok())
    1479             :         {
    1480           0 :             self()->info("SSH backend: libssh2");
    1481           0 :             return cached;
    1482             :         }
    1483             :     }
    1484             : 
    1485           0 :     cached = ESYSREPO_RESULT(ResultCode::GIT_SSH_BACKEND_NOT_SUPPORTED, "No usable SSH backend");
    1486           0 :     return cached;
    1487         202 : }
    1488             : 
    1489         192 : void GitImpl::log_ssh_auth_info_once(Git *self, const std::string &msg)
    1490             : {
    1491         192 :     static std::mutex mutex;
    1492         192 :     static bool logged = false;
    1493             : 
    1494         192 :     std::lock_guard<std::mutex> lock(mutex);
    1495         192 :     if (logged) return;
    1496           1 :     logged = true;
    1497           1 :     if (self != nullptr) self->info(msg);
    1498         192 : }
    1499             : 
    1500           0 : int GitImpl::try_default_ssh_key(git_credential **out, const char *username, int key_index,
    1501             :                                  std::string *used_private_key)
    1502             : {
    1503           0 :     if (key_index < 0) return -1;
    1504             : 
    1505             : #ifdef _WIN32
    1506             :     const char *home_env = "USERPROFILE";
    1507             : #else
    1508           0 :     const char *home_env = "HOME";
    1509             : #endif
    1510             : 
    1511           0 :     std::string home;
    1512             : #ifdef _WIN32
    1513             :     char *buffer = nullptr;
    1514             :     size_t length = 0;
    1515             :     if (_dupenv_s(&buffer, &length, home_env) == 0 && buffer != nullptr)
    1516             :     {
    1517             :         home = buffer;
    1518             :         free(buffer);
    1519             :     }
    1520             : #else
    1521           0 :     if (const char *value = std::getenv(home_env)) home = value;
    1522             : #endif
    1523           0 :     if (home.empty()) return -1;
    1524             : 
    1525             :     static const char *key_names[] = {"id_ed25519", "id_ecdsa", "id_rsa", "id_dsa"};
    1526             :     int found = 0;
    1527           0 :     for (const char *key_name : key_names)
    1528             :     {
    1529           0 :         boost::filesystem::path private_key = boost::filesystem::path(home) / ".ssh" / key_name;
    1530           0 :         if (!boost::filesystem::exists(private_key)) continue;
    1531             : 
    1532           0 :         if (found != key_index)
    1533             :         {
    1534           0 :             ++found;
    1535           0 :             continue;
    1536             :         }
    1537             : 
    1538           0 :         boost::filesystem::path public_key = private_key;
    1539           0 :         public_key += ".pub";
    1540             : 
    1541           0 :         const std::string private_str = private_key.string();
    1542           0 :         const std::string public_str = public_key.string();
    1543           0 :         const char *public_path = boost::filesystem::exists(public_key) ? public_str.c_str() : nullptr;
    1544             : 
    1545           0 :         if (used_private_key != nullptr) *used_private_key = private_str;
    1546             : 
    1547           0 :         return git_credential_ssh_key_new(out, username, public_path, private_str.c_str(), "");
    1548           0 :     }
    1549             : 
    1550             :     return -1;
    1551           0 : }
    1552             : 
    1553        2247 : int GitImpl::libgit2_sideband_progress_cb(const char *str, int len, void *data)
    1554             : {
    1555        2247 :     auto impl = static_cast<GitImpl *>(data);
    1556             : 
    1557        2247 :     if (impl == nullptr) return 0;
    1558             : 
    1559        2247 :     std::string txt(str, str + len);
    1560             : 
    1561        2247 :     int result = impl->self()->handle_sideband_progress(txt);
    1562        2247 :     return result;
    1563        2247 : }
    1564             : 
    1565      106260 : int GitImpl::libgit2_transfer_progress_cb(const git_indexer_progress *stats, void *payload)
    1566             : {
    1567      106260 :     auto impl = static_cast<GitImpl *>(payload);
    1568             : 
    1569      106260 :     if (impl == nullptr) return 0;
    1570             : 
    1571      106260 :     git::Progress progress;
    1572             : 
    1573             :     // std::cout << "[libgit2_transfer_progress_cb]" << std::endl;
    1574             : 
    1575      106260 :     progress.set_total_objects(static_cast<int>(stats->total_objects));
    1576      106260 :     progress.set_received_objects(static_cast<int>(stats->received_objects));
    1577      106260 :     progress.set_indexed_objects(static_cast<int>(stats->indexed_objects));
    1578      106260 :     progress.set_total_deltas(static_cast<int>(stats->total_deltas));
    1579      106260 :     progress.set_indexed_deltas(static_cast<int>(stats->indexed_deltas));
    1580      106260 :     progress.set_received_bytes(static_cast<std::int64_t>(stats->received_bytes));
    1581             : 
    1582      106260 :     if (stats->received_objects == stats->total_objects)
    1583             :     {
    1584       39807 :         progress.set_fetch_step(git::FetchStep::RESOLVING);
    1585             : 
    1586       39807 :         if (stats->indexed_deltas == stats->total_deltas)
    1587             :         {
    1588         385 :             progress.set_done(true);
    1589         385 :             progress.set_percentage(git::Progress::MAX_PERCENTAGE);
    1590             :         }
    1591             :         else
    1592             :         {
    1593       39422 :             double percentage = (100.0 * stats->indexed_deltas) / stats->total_deltas;
    1594             : 
    1595       39422 :             progress.set_done(false);
    1596       39422 :             progress.set_percentage(static_cast<int>(percentage));
    1597             :         }
    1598             :     }
    1599       66453 :     else if (stats->total_objects > 0)
    1600             :     {
    1601       66453 :         progress.set_fetch_step(git::FetchStep::RECEIVING);
    1602       66453 :         if (stats->received_objects == stats->total_objects)
    1603             :         {
    1604           0 :             progress.set_done(true);
    1605           0 :             progress.set_percentage(git::Progress::MAX_PERCENTAGE);
    1606             :         }
    1607             :         else
    1608             :         {
    1609       66453 :             double percentage = (100.0 * stats->received_objects) / stats->total_objects;
    1610             : 
    1611       66453 :             progress.set_done(false);
    1612       66453 :             progress.set_percentage(static_cast<int>(percentage));
    1613             :         }
    1614             :     }
    1615             : 
    1616      106260 :     impl->self()->notify_progress_throttled(progress);
    1617             : 
    1618             :     return 0;
    1619             : }
    1620             : 
    1621         944 : int GitImpl::libgit2_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data)
    1622             : {
    1623         944 :     std::string a_str;
    1624         944 :     std::string b_str;
    1625         944 :     auto self = static_cast<GitImpl *>(data);
    1626         944 :     std::ostringstream oss;
    1627         944 :     git::UpdateTip update_tip;
    1628             : 
    1629         944 :     auto result = self->convert_bin_hex(*b, b_str);
    1630         944 :     if (result.error()) return -1;
    1631             : 
    1632         944 :     update_tip.set_ref_name(refname);
    1633             : 
    1634         944 :     if (git_oid_is_zero(a))
    1635             :     {
    1636             :         // oss << "[new]     " << b_str << " " << refname;
    1637             :         // std::cout << oss.str() << std::endl;
    1638         944 :         update_tip.set_type(git::UpdateTipType::NEW);
    1639         944 :         update_tip.set_new_oid(b_str);
    1640             :     }
    1641             :     else
    1642             :     {
    1643           0 :         update_tip.set_type(git::UpdateTipType::UPDATE);
    1644           0 :         result = self->convert_bin_hex(*a, a_str);
    1645           0 :         if (result.error()) return -1;
    1646             : 
    1647           0 :         update_tip.set_new_oid(b_str);
    1648           0 :         update_tip.set_new_oid(b_str);
    1649             :         // oss << "[updated] " << a_str << ".." << b_str << " " << refname;
    1650             :         // std::cout << oss.str() << std::endl;
    1651             :     }
    1652             :     return 0;
    1653         944 : }
    1654             : 
    1655          10 : int GitImpl::libgit2_pack_progress_cb(int /*stage*/, uint32_t /*current*/, uint32_t /*total*/, void * /*payload*/)
    1656             : {
    1657          10 :     return 0;
    1658             : }
    1659             : 
    1660         193 : Result_t<bool> GitImpl::is_ssh_agent_running()
    1661             : {
    1662         193 :     bool present = m_ssh.is_agent_present();
    1663             : 
    1664         386 :     if (present) self()->debug(0, "SSH agent detected");
    1665             : 
    1666         193 :     return present;
    1667             : }
    1668             : 
    1669          23 : Result GitImpl::merge_analysis(const std::vector<std::string> &refs, git::MergeAnalysisResult &merge_analysis_result,
    1670             :                                std::vector<git::CommitHash> &commits)
    1671             : {
    1672          23 :     Result result;
    1673          23 :     ETRC_CALL_RET_BEGIN(result, refs, merge_analysis_result, commits);
    1674          23 :     ETRC_CALL_RET_OUT_END(merge_analysis_result, commits);
    1675             : 
    1676          23 :     git_repository_state_t state = GIT_REPOSITORY_STATE_NONE;
    1677          23 :     git_merge_analysis_t analysis = GIT_MERGE_ANALYSIS_NONE;
    1678          23 :     git_merge_preference_t preference = GIT_MERGE_PREFERENCE_NONE;
    1679          23 :     git_annotated_commit *annotated = nullptr;
    1680          23 :     git::CommitHash commit;
    1681             : 
    1682          23 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    1683             : 
    1684          23 :     state = static_cast<git_repository_state_t>(git_repository_state(m_repo));
    1685          23 :     if (state != GIT_REPOSITORY_STATE_NONE)
    1686             :     {
    1687           0 :         return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    1688             :     }
    1689             : 
    1690          23 :     std::vector<git_annotated_commit *> annotated_vec;
    1691          23 :     std::vector<git_oid> oids;
    1692          23 :     const git_oid *target_oid = nullptr;
    1693          23 :     std::string hash;
    1694             : 
    1695          46 :     for (auto &ref : refs)
    1696             :     {
    1697          23 :         result = resolve_ref(&annotated, ref);
    1698          23 :         if (result.ok())
    1699             :         {
    1700          23 :             target_oid = git_annotated_commit_id(annotated);
    1701          23 :             oids.push_back(*target_oid);
    1702          23 :             annotated_vec.push_back(annotated);
    1703             : 
    1704          23 :             result = convert_bin_hex(*target_oid, hash);
    1705          23 :             if (result.ok())
    1706             :             {
    1707          23 :                 commit.set_hash(hash);
    1708          23 :                 commits.push_back(commit);
    1709             :             }
    1710             :         }
    1711             :     }
    1712             : 
    1713          23 :     int result_int = git_merge_analysis(&analysis, &preference, m_repo,
    1714          23 :                                         (const git_annotated_commit **)annotated_vec.data(), annotated_vec.size());
    1715             :     //! \TODO handling error is missing
    1716          46 :     for (auto annotated : annotated_vec) git_annotated_commit_free(annotated);
    1717             : 
    1718          23 :     if (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE)
    1719          21 :         merge_analysis_result = git::MergeAnalysisResult::UP_TO_DATE;
    1720           2 :     else if (analysis & GIT_MERGE_ANALYSIS_NORMAL)
    1721             :     {
    1722           2 :         if (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)
    1723           2 :             merge_analysis_result = git::MergeAnalysisResult::FASTFORWARD;
    1724             :         else
    1725           0 :             merge_analysis_result = git::MergeAnalysisResult::NORMAL;
    1726             :     }
    1727           0 :     else if (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)
    1728           0 :         merge_analysis_result = git::MergeAnalysisResult::FASTFORWARD;
    1729           0 :     else if (analysis & GIT_MERGE_ANALYSIS_UNBORN)
    1730           0 :         merge_analysis_result = git::MergeAnalysisResult::UNBORN;
    1731             :     else
    1732           0 :         merge_analysis_result = git::MergeAnalysisResult::NONE;
    1733          46 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    1734          46 : }
    1735             : 
    1736          75 : Result GitImpl::find_remote(Guard<git_remote> &remote, const std::string &remote_name)
    1737             : {
    1738          75 :     Result result;
    1739          75 :     ETRC_CALL_RET_BEGIN(result, remote, remote_name);
    1740          75 :     ETRC_CALL_RET_OUT_END(remote);
    1741             : 
    1742          75 :     if (m_repo == nullptr) return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
    1743             : 
    1744          75 :     std::string remote_name_str;
    1745             : 
    1746          75 :     if (!remote_name.empty())
    1747             :     {
    1748           7 :         int result_int = git_remote_lookup(remote.get_p(), m_repo, remote_name.c_str());
    1749           7 :         if (result_int < 0)
    1750             :         {
    1751           0 :             std::string err_str = "The given remote is not known : " + remote_name;
    1752             :             // The remote is not known
    1753           0 :             self()->error(err_str);
    1754           0 :             return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, err_str));
    1755           0 :         }
    1756             : 
    1757           7 :         remote_name_str = git_remote_name(remote.get());
    1758             :     }
    1759             :     else
    1760             :     {
    1761          68 :         git::Branches branches;
    1762             : 
    1763          68 :         result = get_branches(branches);
    1764          68 :         if (result.error())
    1765             :         {
    1766           0 :             std::string err_str = "Couldn't get the branches for this git repo";
    1767           0 :             self()->error(err_str);
    1768           0 :             return ETRC_RET(ESYSREPO_RESULT(result, err_str));
    1769           0 :         }
    1770             : 
    1771          68 :     if (branches.size() == 0)
    1772             :     {
    1773             :         // After init+fetch with no local checkout yet, fall back to "origin"
    1774           0 :         int result_int = git_remote_lookup(remote.get_p(), m_repo, "origin");
    1775           0 :         if (result_int == 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    1776             : 
    1777           0 :         self()->error("No branches found for this git repo");
    1778           0 :         return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_NO_BRANCH_FOUND));
    1779             :     }
    1780             : 
    1781          68 :         branches.sort();
    1782             : 
    1783          68 :         remote_name_str = branches.get()[0]->get_remote_name();
    1784             : 
    1785          68 :         int result_int = git_remote_lookup(remote.get_p(), m_repo, remote_name_str.c_str());
    1786          68 :         if (result_int < 0)
    1787             :         {
    1788           0 :             std::string err_str = "The given remote is not known : " + remote_name_str;
    1789             :             // The remote is not known
    1790           0 :             self()->error(err_str);
    1791           0 :             return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, err_str));
    1792           0 :         }
    1793          68 :     }
    1794             : 
    1795         150 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    1796          75 : }
    1797             : 
    1798           6 : Result GitImpl::get_commit_hash_from_branch(const std::string &branch, git_oid &oid)
    1799             : {
    1800           6 :     Guard<git_annotated_commit> annotated_commit;
    1801           6 :     Guard<git_reference> input_branch_ref;
    1802           6 :     std::string new_ref = branch;
    1803           6 :     Guard<git_commit> target_commit;
    1804             : 
    1805             :     // Guard<git_object> treeish;
    1806             : 
    1807             :     // Guard<git_reference> ref;
    1808             :     // Guard<git_reference> branch_ref;
    1809             : 
    1810           6 :     Result result = resolve_ref(input_branch_ref.get_p(), annotated_commit.get_p(), branch);
    1811           6 :     if (result.error())
    1812             :     {
    1813           0 :         self()->debug(0, "branch not found : " + branch);
    1814             :         // In the case where the content of branch doesn't have the full qualifier,
    1815             :         // try to to guess
    1816             : 
    1817           0 :         result = find_ref(input_branch_ref.get_p(), annotated_commit.get_p(), branch, new_ref);
    1818           0 :         if (result.error())
    1819             :         {
    1820           0 :             return check_error(ESYSREPO_RESULT(result));
    1821             :         }
    1822             :     }
    1823             : 
    1824           6 :     int result_int = git_commit_lookup(target_commit.get_p(), m_repo, git_annotated_commit_id(annotated_commit.get()));
    1825           6 :     if (result_int < 0) return check_error(ESYSREPO_RESULT(ResultCode::GIT_FIND_REF_FAILED));
    1826             : 
    1827           6 :     const git_oid *target_oid = git_commit_id(target_commit.get());
    1828           6 :     if (target_oid == nullptr) ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR);
    1829           6 :     oid = *target_oid;
    1830           6 :     return ESYSREPO_RESULT(ResultCode::OK);
    1831           6 : }
    1832             : 
    1833          68 : Result GitImpl::fetch(const std::string &remote_str)
    1834             : {
    1835          68 :     Result result;
    1836          68 :     ETRC_CALL_RET(result, remote_str);
    1837             : 
    1838          68 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    1839             : 
    1840          68 :     result = ensure_ssh_backend();
    1841          68 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    1842             : 
    1843          68 :     Guard<git_remote> remote;
    1844             : 
    1845          68 :     result = find_remote(remote, remote_str);
    1846          68 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    1847             : 
    1848          68 :     git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
    1849          68 :     m_ssh_cred_attempt = 0;
    1850          68 :     m_ssh_agent_tried = false;
    1851          68 :     setup_remote_callbacks(fetch_opts.callbacks);
    1852             : 
    1853          68 :     int result_int = git_remote_fetch(remote.get(), nullptr, &fetch_opts, "fetch");
    1854          68 :     if (result_int < 0)
    1855             :     {
    1856           0 :         std::string err_str = "Fetch failed";
    1857           0 :         self()->error(err_str);
    1858           0 :         return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, err_str));
    1859           0 :     }
    1860             : 
    1861          68 :     const git_indexer_progress *stats = git_remote_stats(remote.get());
    1862             : 
    1863             :     //! \TODO what to do with the stats;
    1864             : 
    1865         136 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    1866          68 : }
    1867             : 
    1868           2 : Result GitImpl::fetch_all_notes(const std::string &remote_str)
    1869             : {
    1870           2 :     Result result;
    1871           2 :     ETRC_CALL_RET(result, remote_str);
    1872             : 
    1873           2 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    1874             : 
    1875           2 :     result = ensure_ssh_backend();
    1876           2 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    1877             : 
    1878           2 :     m_notes.clear();
    1879             : 
    1880           2 :     Guard<git_remote> remote;
    1881             : 
    1882           2 :     result = find_remote(remote, remote_str);
    1883           2 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    1884             : 
    1885           2 :     Guard<git_refspec> ref_spec;
    1886           2 :     int result_int = git_refspec_parse(ref_spec.get_p(), "refs/notes/*:refs/notes/*", 1);
    1887           2 :     if (result_int != 0) ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
    1888             : 
    1889           2 :     git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
    1890           2 :     m_ssh_cred_attempt = 0;
    1891           2 :     m_ssh_agent_tried = false;
    1892           2 :     setup_remote_callbacks(fetch_opts.callbacks);
    1893             : 
    1894           2 :     git_strarray refspecs;
    1895           2 :     char refspecs_str[] = "refs/notes/*:refs/notes/*";
    1896           2 :     char *refspecs_array[1];
    1897           2 :     refspecs_array[0] = refspecs_str;
    1898           2 :     refspecs.count = 1;
    1899           2 :     refspecs.strings = refspecs_array;
    1900             : 
    1901           2 :     result_int = git_remote_fetch(remote.get(), &refspecs, &fetch_opts, "fetch");
    1902           2 :     if (result_int < 0)
    1903             :     {
    1904           0 :         std::string err_str = "Fetch failed";
    1905           0 :         self()->error(err_str);
    1906           0 :         return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, err_str));
    1907           0 :     }
    1908             : 
    1909           4 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    1910           4 : }
    1911             : 
    1912           4 : Result GitImpl::add_note(git::NoteId &note_id, const std::string &reference, const std::string &message, bool overwrite)
    1913             : {
    1914           4 :     Result result;
    1915           4 :     ETRC_CALL_RET_BEGIN(result, note_id, reference, message, overwrite);
    1916           4 :     ETRC_CALL_RET_OUT_END(note_id);
    1917             : 
    1918           4 :     if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
    1919           4 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    1920           8 :     if (self()->get_author() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::NO_AUTHOR_GIVEN));
    1921             : 
    1922           4 :     std::string the_ref = reference;
    1923           4 :     Guard<git_signature> author_sig;
    1924           4 :     Guard<git_signature> committer_sig;
    1925             : 
    1926           4 :     the_ref = normalize_notes_ref(reference);
    1927             : 
    1928           4 :     result = get_sigs(author_sig.get_p(), committer_sig.get_p());
    1929           4 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
    1930             : 
    1931           4 :     git_oid note_iod;
    1932           4 :     git_oid oid_last_commit;
    1933             : 
    1934             :     // resolve HEAD into a SHA1
    1935           4 :     auto result_int = git_reference_name_to_id(&oid_last_commit, m_repo, "HEAD");
    1936           4 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
    1937             : 
    1938           4 :     result_int = git_note_create(&note_iod, m_repo, the_ref.c_str(), author_sig.get(), committer_sig.get(),
    1939             :                                  &oid_last_commit, message.c_str(), overwrite);
    1940           4 :     if (result_int != 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
    1941             : 
    1942           4 :     m_notes.clear();
    1943             : 
    1944           4 :     result = get_commit_hash(note_iod, note_id);
    1945           4 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
    1946             : 
    1947           4 :     result = get_commit_hash(oid_last_commit, note_id.get_commit_hash());
    1948           4 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
    1949             : 
    1950           4 :     note_id.set_reference(reference);
    1951             : 
    1952           8 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    1953           8 : }
    1954             : 
    1955           1 : Result GitImpl::remove_note(const git::NoteId &note_id)
    1956             : {
    1957           1 :     Result result;
    1958           1 :     ETRC_CALL_RET(result, note_id);
    1959             : 
    1960           1 :     if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
    1961           1 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    1962           2 :     if (self()->get_author() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::NO_AUTHOR_GIVEN));
    1963             : 
    1964           1 :     git_oid note_oid;
    1965             : 
    1966           1 :     int result_int = git_oid_fromstr(&note_oid, note_id.get_hash().c_str());
    1967           1 :     if (result_int != 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
    1968             : 
    1969           2 :     return ETRC_RET(remove_note(note_id.get_commit_hash(), note_id.get_reference()));
    1970           1 : }
    1971             : 
    1972           2 : Result GitImpl::remove_note(const git::CommitHash &commit_hash, const std::string &reference)
    1973             : {
    1974           2 :     Result result;
    1975           2 :     ETRC_CALL_RET(result, commit_hash, reference);
    1976             : 
    1977           2 :     if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
    1978           2 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    1979           4 :     if (self()->get_author() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::NO_AUTHOR_GIVEN));
    1980             : 
    1981           2 :     Guard<git_signature> author_sig;
    1982           2 :     Guard<git_signature> committer_sig;
    1983           2 :     git_oid target_oid;
    1984             : 
    1985           2 :     result = get_sigs(author_sig.get_p(), committer_sig.get_p());
    1986           2 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
    1987             : 
    1988           2 :     int result_int = git_oid_fromstr(&target_oid, commit_hash.get_hash().c_str());
    1989           2 :     if (result_int != 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
    1990             : 
    1991           2 :     std::string note_ref = normalize_notes_ref(reference);
    1992           2 :     result_int = git_note_remove(m_repo, note_ref.c_str(), author_sig.get(), committer_sig.get(), &target_oid);
    1993             : 
    1994           2 :     if (result_int != 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
    1995             : 
    1996           2 :     m_notes.clear();
    1997           4 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    1998           4 : }
    1999             : 
    2000           1 : Result GitImpl::remove_note_last_commit(const std::string &reference)
    2001             : {
    2002           1 :     Result result;
    2003           1 :     ETRC_CALL_RET(result, reference);
    2004             : 
    2005           1 :     git::CommitHash commit_hash;
    2006           1 :     result = get_last_commit(commit_hash);
    2007           1 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
    2008             : 
    2009           2 :     return ETRC_RET(remove_note(commit_hash, reference));
    2010           1 : }
    2011             : 
    2012           2 : Result GitImpl::get_refspec(std::string &refspec_str, const std::string &src_ref, const std::string &dst_ref)
    2013             : {
    2014           2 :     Result result;
    2015           2 :     ETRC_CALL_RET_BEGIN(result, refspec_str, src_ref, dst_ref);
    2016           2 :     ETRC_CALL_RET_OUT_END(refspec_str);
    2017             : 
    2018           2 :     if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
    2019           2 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    2020             : 
    2021           2 :     std::string src_ref_name;
    2022           2 :     std::string dst_ref_name;
    2023             : 
    2024           2 :     if (src_ref.empty() && dst_ref.empty())
    2025             :     {
    2026             :         // Push the head
    2027           0 :         Guard<git_reference> head_ref;
    2028             : 
    2029           0 :         int result_int = git_repository_head(head_ref.get_p(), m_repo);
    2030           0 :         if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    2031             : 
    2032           0 :         src_ref_name = git_reference_name(head_ref.get());
    2033           0 :     }
    2034           2 :     if (!src_ref.empty())
    2035             :     {
    2036           2 :         Guard<git_reference> git_src_ref;
    2037           2 :         result = resolve_ref(git_src_ref.get_p(), src_ref);
    2038           2 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    2039             : 
    2040           2 :         src_ref_name = git_reference_name(git_src_ref.get());
    2041           2 :     }
    2042           2 :     if (!dst_ref.empty())
    2043             :     {
    2044           0 :         Guard<git_reference> git_dst_ref;
    2045           0 :         result = resolve_ref(git_dst_ref.get_p(), src_ref);
    2046           0 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    2047             : 
    2048           0 :         dst_ref_name = git_reference_name(git_dst_ref.get());
    2049           0 :     }
    2050             :     else
    2051           2 :         dst_ref_name = src_ref_name;
    2052             : 
    2053           2 :     refspec_str = src_ref_name;
    2054           2 :     refspec_str += ":";
    2055           2 :     refspec_str += dst_ref_name;
    2056           4 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    2057           4 : }
    2058             : 
    2059           2 : Result GitImpl::push(const std::string &remote, const std::string &src_ref, const std::string &dst_ref)
    2060             : {
    2061           2 :     Result result;
    2062           2 :     ETRC_CALL_RET(result, src_ref, dst_ref);
    2063             : 
    2064           2 :     result = ensure_ssh_backend();
    2065           2 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    2066             : 
    2067           2 :     git_push_options options;
    2068           2 :     Guard<git_remote> remote_git;
    2069           2 :     std::string refspec_str;
    2070             : 
    2071           2 :     result = get_refspec(refspec_str, src_ref, dst_ref);
    2072           2 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    2073             : 
    2074           2 :     char *refspec = (char *)refspec_str.c_str();
    2075           2 :     const git_strarray refspecs = {&refspec, 1};
    2076             : 
    2077           2 :     int result_int = git_remote_lookup(remote_git.get_p(), m_repo, remote.c_str());
    2078           2 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    2079             : 
    2080           2 :     result_int = git_push_options_init(&options, GIT_PUSH_OPTIONS_VERSION);
    2081           2 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    2082             : 
    2083           2 :     m_ssh_cred_attempt = 0;
    2084           2 :     m_ssh_agent_tried = false;
    2085           2 :     setup_remote_callbacks(options.callbacks);
    2086             : 
    2087           2 :     result_int = git_remote_push(remote_git.get(), &refspecs, &options);
    2088           2 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    2089             : 
    2090           4 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    2091           4 : }
    2092             : 
    2093           1 : Result GitImpl::push_notes(const std::string &remote, const std::string &reference)
    2094             : {
    2095           1 :     Result result;
    2096           1 :     ETRC_CALL_RET(result, remote, reference);
    2097             : 
    2098           1 :     result = ensure_ssh_backend();
    2099           1 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    2100             : 
    2101           1 :     git_push_options options;
    2102           1 :     Guard<git_remote> remote_git;
    2103           1 :     std::string note_ref = normalize_notes_ref(reference);
    2104           1 :     char *refspec = (char *)note_ref.c_str();
    2105           1 :     const git_strarray refspecs = {&refspec, 1};
    2106             : 
    2107           1 :     int result_int = git_remote_lookup(remote_git.get_p(), m_repo, remote.c_str());
    2108           1 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    2109             : 
    2110           1 :     result_int = git_push_options_init(&options, GIT_PUSH_OPTIONS_VERSION);
    2111           1 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    2112             : 
    2113           1 :     m_ssh_cred_attempt = 0;
    2114           1 :     m_ssh_agent_tried = false;
    2115           1 :     setup_remote_callbacks(options.callbacks);
    2116             : 
    2117           1 :     result_int = git_remote_push(remote_git.get(), &refspecs, &options);
    2118           1 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    2119             : 
    2120           2 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    2121           2 : }
    2122             : 
    2123           2 : Result GitImpl::resolve_ref(git_reference **ref, const std::string &ref_str)
    2124             : {
    2125           2 :     Guard<git_object> git_obj;
    2126           2 :     int result = 0;
    2127             : 
    2128           2 :     assert(ref != nullptr);
    2129           2 :     assert(m_repo != nullptr);
    2130             : 
    2131           2 :     result = git_reference_dwim(ref, m_repo, ref_str.c_str());
    2132           2 :     if (result == GIT_OK) return ESYSREPO_RESULT(ResultCode::OK);
    2133             : 
    2134           0 :     result = git_revparse_single(git_obj.get_p(), m_repo, ref_str.c_str());
    2135           0 :     if (result == GIT_OK) return ESYSREPO_RESULT(ResultCode::OK);
    2136             : 
    2137           0 :     return ESYSREPO_RESULT(ResultCode::GENERIC_ERROR);
    2138           2 : }
    2139             : 
    2140          23 : Result GitImpl::resolve_ref(git_annotated_commit **commit, const std::string &ref)
    2141             : {
    2142          23 :     git_reference *git_ref = nullptr;
    2143          23 :     git_object *git_obj = nullptr;
    2144          23 :     int result = 0;
    2145             : 
    2146          23 :     assert(commit != nullptr);
    2147          23 :     assert(m_repo != nullptr);
    2148             : 
    2149          23 :     result = git_reference_dwim(&git_ref, m_repo, ref.c_str());
    2150          23 :     if (result == GIT_OK)
    2151             :     {
    2152          23 :         const char *name = git_reference_name(git_ref);
    2153             : 
    2154             :         //! \TODO remote this
    2155          23 :         const char *branch_name = nullptr;
    2156          23 :         git_branch_name(&branch_name, git_ref);
    2157             : 
    2158          23 :         git_annotated_commit_from_ref(commit, m_repo, git_ref);
    2159          23 :         git_reference_free(git_ref);
    2160          23 :         return ESYSREPO_RESULT(ResultCode::OK);
    2161             :     }
    2162             : 
    2163           0 :     result = git_revparse_single(&git_obj, m_repo, ref.c_str());
    2164           0 :     if (result == GIT_OK)
    2165             :     {
    2166           0 :         result = git_annotated_commit_lookup(commit, m_repo, git_object_id(git_obj));
    2167           0 :         git_object_free(git_obj);
    2168           0 :         return ESYSREPO_RESULT(ResultCode::OK);
    2169             :     }
    2170             : 
    2171           0 :     return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
    2172             : }
    2173             : 
    2174         183 : Result GitImpl::resolve_ref(git_reference **ref, git_annotated_commit **commit, const std::string &ref_str)
    2175             : {
    2176         183 :     Guard<git_object> git_obj;
    2177         183 :     int result = 0;
    2178             : 
    2179         183 :     assert(commit != nullptr);
    2180         183 :     assert(ref != nullptr);
    2181         183 :     assert(m_repo != nullptr);
    2182             : 
    2183         183 :     result = git_reference_dwim(ref, m_repo, ref_str.c_str());
    2184         183 :     if (result == GIT_OK)
    2185             :     {
    2186          97 :         git_annotated_commit_from_ref(commit, m_repo, *ref);
    2187          97 :         return ESYSREPO_RESULT(ResultCode::OK);
    2188             :     }
    2189             : 
    2190          86 :     result = git_revparse_single(git_obj.get_p(), m_repo, ref_str.c_str());
    2191          86 :     if (result == GIT_OK)
    2192             :     {
    2193          18 :         result = git_annotated_commit_lookup(commit, m_repo, git_object_id(git_obj.get()));
    2194             :     }
    2195             : 
    2196          86 :     if (result == GIT_OK) return ESYSREPO_RESULT(ResultCode::OK);
    2197          68 :     return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
    2198         183 : }
    2199             : 
    2200           0 : Result GitImpl::find_ref(git_annotated_commit **commit, const std::string &ref, std::string &new_ref)
    2201             : {
    2202           0 :     std::vector<git::Remote> remotes;
    2203           0 :     int found_remotes = 0;
    2204             : 
    2205           0 :     Result rresult = get_remotes(remotes);
    2206           0 :     if (rresult.error()) return ESYSREPO_RESULT(rresult);
    2207             : 
    2208           0 :     int result = 0;
    2209             : 
    2210           0 :     for (auto &remote : remotes)
    2211             :     {
    2212           0 :         new_ref = "refs/remotes/" + remote.get_name() + "/" + ref;
    2213             : 
    2214           0 :         rresult = resolve_ref(commit, new_ref);
    2215           0 :         if (rresult.ok())
    2216             :         {
    2217           0 :             self()->debug(0, "found branch : " + new_ref);
    2218           0 :             ++found_remotes;
    2219             :         }
    2220             :     }
    2221           0 :     if (found_remotes == 1) return ESYSREPO_RESULT(ResultCode::OK);
    2222           0 :     return ESYSREPO_RESULT(ResultCode::GIT_FIND_REF_FAILED);
    2223           0 : }
    2224             : 
    2225          60 : Result GitImpl::find_ref(git_reference **ref, git_annotated_commit **commit, const std::string &ref_str,
    2226             :                          std::string &new_ref)
    2227             : {
    2228          60 :     std::vector<git::Remote> remotes;
    2229          60 :     int found_remotes = 0;
    2230             : 
    2231          60 :     Result rresult = get_remotes(remotes);
    2232          60 :     if (rresult.error()) return ESYSREPO_RESULT(rresult);
    2233             : 
    2234          60 :     int result = 0;
    2235         121 :     for (auto &remote : remotes)
    2236             :     {
    2237          61 :         new_ref = "refs/remotes/" + remote.get_name() + "/" + ref_str;
    2238             : 
    2239          61 :         rresult = resolve_ref(ref, commit, new_ref);
    2240          61 :         if (rresult.ok())
    2241             :         {
    2242          53 :             self()->debug(0, "found branch : " + new_ref);
    2243          53 :             ++found_remotes;
    2244             :         }
    2245             :     }
    2246          60 :     if (found_remotes == 1) return ESYSREPO_RESULT(ResultCode::OK);
    2247           7 :     return ESYSREPO_RESULT(ResultCode::GIT_FIND_REF_FAILED);
    2248          60 : }
    2249             : 
    2250        1143 : Result GitImpl::convert_bin_hex(const git_oid &oid, std::string &hex_str)
    2251             : {
    2252        1143 :     char temp[GIT_OID_HEXSZ + 1];
    2253             : 
    2254        1143 :     int result = git_oid_fmt(temp, &oid);
    2255        1143 :     if (result < 0) return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
    2256        1143 :     temp[GIT_OID_HEXSZ] = 0;
    2257        1143 :     hex_str = std::string(temp);
    2258        1143 :     return ESYSREPO_RESULT(ResultCode::OK);
    2259             : }
    2260             : 
    2261          15 : Result GitImpl::convert_hex_bin(const std::string &hex_str, git_oid &oid)
    2262             : {
    2263          15 :     int result = git_oid_fromstrp(&oid, hex_str.c_str());
    2264          15 :     if (result == GIT_OK) return ESYSREPO_RESULT(ResultCode::OK);
    2265           0 :     return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
    2266             : }
    2267             : 
    2268           0 : const std::string &GitImpl::get_version()
    2269             : {
    2270           0 :     return s_get_version();
    2271             : }
    2272             : 
    2273           0 : const std::string &GitImpl::get_lib_name()
    2274             : {
    2275           0 :     return s_get_lib_name();
    2276             : }
    2277             : 
    2278         163 : void GitImpl::convert(git::BranchType branch_type, git_branch_t &list_flags)
    2279             : {
    2280         163 :     switch (branch_type)
    2281             :     {
    2282           0 :         case git::BranchType::ALL: list_flags = GIT_BRANCH_ALL; break;
    2283         150 :         case git::BranchType::LOCAL: list_flags = GIT_BRANCH_LOCAL; break;
    2284          13 :         case git::BranchType::REMOTE: list_flags = GIT_BRANCH_REMOTE; break;
    2285           0 :         default: list_flags = GIT_BRANCH_LOCAL;
    2286             :     }
    2287         163 : }
    2288             : 
    2289           0 : void GitImpl::set_agent_identity_path(const std::string &agent_identity_path)
    2290             : {
    2291           0 :     m_agent_identity_path = agent_identity_path;
    2292           0 : }
    2293             : 
    2294           0 : const std::string &GitImpl::get_agent_identity_path() const
    2295             : {
    2296           0 :     return m_agent_identity_path;
    2297             : }
    2298             : 
    2299         429 : void GitImpl::set_logger_if(std::shared_ptr<log::Logger_if> logger_if)
    2300             : {
    2301         858 :     m_ssh.set_logger_if(logger_if);
    2302         429 : }
    2303             : 
    2304          12 : git_repository *GitImpl::get_repo()
    2305             : {
    2306          12 :     return m_repo;
    2307             : }
    2308             : 
    2309           6 : Result GitImpl::get_sigs(git_signature **author, git_signature **committer)
    2310             : {
    2311           6 :     std::shared_ptr<git::Person> person = self()->get_author();
    2312             : 
    2313           6 :     int result_int = git_signature_now(author, person->get_name().c_str(), person->get_email().c_str());
    2314           6 :     if (result_int < 0) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR);
    2315             : 
    2316           6 :     if (self()->get_committer() != nullptr) person = self()->get_committer();
    2317             : 
    2318           6 :     result_int = git_signature_now(committer, person->get_name().c_str(), person->get_email().c_str());
    2319           6 :     if (result_int < 0) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR);
    2320             : 
    2321           6 :     return ESYSREPO_RESULT(ResultCode::OK);
    2322           6 : }
    2323             : 
    2324          19 : std::string GitImpl::normalize_notes_ref(const std::string &reference)
    2325             : {
    2326          19 :     std::string the_ref = reference;
    2327             : 
    2328          19 :     if (reference.find("refs/notes/") == std::string::npos) the_ref = "refs/notes/" + reference;
    2329             : 
    2330          19 :     return the_ref;
    2331           0 : }
    2332             : 
    2333           2 : const std::string &GitImpl::s_get_version()
    2334             : {
    2335           2 :     static std::string s_version = LIBGIT2_VERSION;
    2336           2 :     return s_version;
    2337             : }
    2338             : 
    2339           2 : const std::string &GitImpl::s_get_lib_name()
    2340             : {
    2341           2 :     static std::string s_lib_name = "libgit2";
    2342           2 :     return s_lib_name;
    2343             : }
    2344             : 
    2345           2 : const std::string &GitImpl::s_get_ssh_version()
    2346             : {
    2347           2 :     static std::string s_version = LIBSSH2_VERSION;
    2348           2 :     return s_version;
    2349             : }
    2350             : 
    2351           2 : const std::string &GitImpl::s_get_ssh_lib_name()
    2352             : {
    2353           2 :     static std::string s_lib_name = "libssh2";
    2354           2 :     return s_lib_name;
    2355             : }
    2356             : 
    2357             : } // namespace esys::repo::libgit2

Generated by: LCOV version 1.14