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: 1129 1471 76.8 %
Date: 2026-08-12 14:26:50 Functions: 76 93 81.7 %

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

Generated by: LCOV version 1.14