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: 1149 1497 76.8 %
Date: 2026-09-26 15:38:18 Functions: 77 94 81.9 %

          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         260 : GitImpl::GitImpl(Git *self)
      51         260 :     : m_self(self)
      52         260 :     , m_notes(self)
      53             : {
      54         261 :     if (s_libgt2 == nullptr) s_libgt2 = std::make_unique<LibGit2>();
      55         260 : }
      56             : 
      57         260 : GitImpl::~GitImpl()
      58             : {
      59         260 :     if (m_repo != nullptr) close_on_error();
      60         260 : }
      61             : 
      62         389 : Result GitImpl::open(const std::string &folder)
      63             : {
      64         389 :     Result result;
      65         389 :     ETRC_CALL_RET(result, folder);
      66             : 
      67         389 :     self()->open_time();
      68             : 
      69         389 :     int result_int = git_repository_open(&m_repo, folder.c_str());
      70         389 :     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         778 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
      77         389 : }
      78             : 
      79        1003 : bool GitImpl::is_open()
      80             : {
      81        1003 :     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         427 : Result GitImpl::close()
     120             : {
     121         427 :     Result result;
     122         427 :     ETRC_CALL_RET_NP(result);
     123             : 
     124         427 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
     125             : 
     126         427 :     git_repository_free(m_repo);
     127             : 
     128         427 :     m_repo = nullptr;
     129             : 
     130         427 :     self()->close_time();
     131             : 
     132         854 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     133         427 : }
     134             : 
     135           1 : void GitImpl::close_on_error()
     136             : {
     137           1 :     ETRC_CALL_NP();
     138           1 :     self()->close_time();
     139           1 :     if (m_repo == nullptr) return;
     140             : 
     141           1 :     git_repository_free(m_repo);
     142           1 :     m_repo = nullptr;
     143           1 : }
     144             : 
     145          57 : Result GitImpl::get_remotes(std::vector<git::Remote> &remotes)
     146             : {
     147          57 :     Result result;
     148          57 :     ETRC_CALL_RET_BEGIN(result, remotes);
     149          57 :     ETRC_CALL_RET_OUT_END(remotes);
     150             : 
     151          57 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
     152             : 
     153          57 :     self()->cmd_start();
     154             : 
     155          57 :     remotes.clear();
     156          57 :     GuardS<git_strarray> data;
     157             : 
     158          57 :     int result_int = git_remote_list(data.get(), m_repo);
     159          57 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     160             : 
     161          57 :     char **p = data.get()->strings;
     162             : 
     163          57 :     if (data.get()->count == 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     164             : 
     165          57 :     if (p == nullptr) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "No data")));
     166             : 
     167         117 :     for (auto idx = 0; idx < data.get()->count; ++idx)
     168             :     {
     169          60 :         char *name = *p;
     170          60 :         if (name == nullptr) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "No name")));
     171             : 
     172          60 :         git::Remote remote;
     173             : 
     174          60 :         remote.set_name(name);
     175             : 
     176          60 :         remotes.push_back(remote);
     177          60 :         p += 1;
     178          60 :     }
     179             : 
     180         117 :     for (auto &remote_item : remotes)
     181             :     {
     182          60 :         Guard<git_remote> remote; // This will automically release the git_remote
     183             : 
     184          60 :         const git_remote_head **refs = nullptr;
     185          60 :         size_t refs_len = 0;
     186          60 :         size_t i = 0;
     187          60 :         git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
     188             : 
     189             :         // Find the remote by name
     190          60 :         result_int = git_remote_lookup(remote.get_p(), m_repo, remote_item.get_name().c_str());
     191          60 :         if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     192             : 
     193          60 :         const char *url = git_remote_url(remote.get());
     194         120 :         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          60 :     }
     199             : 
     200         114 :     return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     201          57 : }
     202             : 
     203          17 : Result GitImpl::get_head_branch_remote(git::Branch &branch, git::Remote &remote)
     204             : {
     205          17 :     Result result;
     206          17 :     ETRC_CALL_RET_BEGIN(result, branch, remote);
     207          17 :     ETRC_CALL_RET_OUT_END(branch, remote);
     208             : 
     209          17 :     if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
     210             : 
     211          17 :     Guard<git_reference> head_ref;
     212             : 
     213          17 :     auto result_int = git_repository_head(head_ref.get_p(), m_repo);
     214          17 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
     215          17 :     auto head_ref_name = git_reference_name(head_ref.get());
     216          17 :     auto head_ref_short = git_reference_shorthand(head_ref.get());
     217             : 
     218          17 :     branch.set_is_head(true);
     219          17 :     branch.set_ref_name(head_ref_name);
     220          17 :     branch.set_name(head_ref_short);
     221          17 :     branch.set_type(git::BranchType::LOCAL);
     222             : 
     223          17 :     result_int = git_repository_head_detached(m_repo);
     224          17 :     if (result_int == 1)
     225           0 :         branch.set_detached(true);
     226          17 :     else if (result_int == 0)
     227          17 :         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          17 :     git_buf remote_name_buf = {nullptr};
     232          17 :     result_int = git_branch_upstream_remote(&remote_name_buf, m_repo, head_ref_name);
     233          17 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
     234             : 
     235          34 :     std::string remote_name(remote_name_buf.ptr, remote_name_buf.ptr + remote_name_buf.size);
     236          17 :     git_buf_dispose(&remote_name_buf);
     237             : 
     238          17 :     branch.set_remote_name(remote_name);
     239          17 :     remote.set_name(remote_name);
     240             : 
     241          17 :     Guard<git_remote> remote_git;
     242          17 :     result = find_remote(remote_git, remote_name.c_str());
     243          17 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     244             : 
     245          17 :     std::string remote_url = git_remote_url(remote_git.get());
     246          17 :     remote.set_url(remote_url);
     247             : 
     248          17 :     Guard<git_reference> upstream_ref;
     249          17 :     result_int = git_branch_upstream(upstream_ref.get_p(), head_ref.get());
     250          17 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
     251             : 
     252          17 :     auto up_ref_name = git_reference_name(upstream_ref.get());
     253          17 :     branch.set_remote_branch(up_ref_name);
     254          17 :     auto up_ref_short = git_reference_shorthand(upstream_ref.get());
     255          17 :     branch.set_remote_branch_name(up_ref_short);
     256          34 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     257          68 : }
     258             : 
     259           6 : Result GitImpl::add_remote(const std::string &name, const std::string &url)
     260             : {
     261           6 :     Result result;
     262           6 :     ETRC_CALL_RET(result, name, url);
     263             : 
     264           6 :     if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
     265             : 
     266           6 :     self()->cmd_start();
     267             : 
     268           6 :     Guard<git_remote> remote;
     269             : 
     270           6 :     int result_int = git_remote_create(remote.get_p(), m_repo, name.c_str(), url.c_str());
     271           6 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     272             : 
     273          12 :     return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     274           6 : }
     275             : 
     276         131 : Result GitImpl::get_branches(git::Branches &branches, git::BranchType branch_type)
     277             : {
     278         131 :     Result result;
     279         131 :     ETRC_CALL_RET_BEGIN(result, branches, branch_type);
     280         131 :     ETRC_CALL_RET_OUT_END(branches);
     281             : 
     282         131 :     if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
     283             : 
     284         131 :     self()->cmd_start();
     285             : 
     286         131 :     Guard<git_branch_iterator> branch_it;
     287         131 :     git_branch_t list_flags = GIT_BRANCH_ALL;
     288             : 
     289         131 :     convert(branch_type, list_flags);
     290             : 
     291         131 :     int result_int = git_branch_iterator_new(branch_it.get_p(), m_repo, list_flags);
     292         131 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     293             : 
     294         271 :     while (true)
     295             :     {
     296         271 :         Guard<git_reference> ref;
     297         271 :         git_branch_t git_branch_type = GIT_BRANCH_ALL;
     298             : 
     299         271 :         result_int = git_branch_next(ref.get_p(), &git_branch_type, branch_it.get());
     300         402 :         if (result_int == GIT_ITEROVER) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     301         140 :         if (result_int < 0) ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     302             : 
     303         140 :         std::shared_ptr<git::Branch> branch = std::make_shared<git::Branch>();
     304         140 :         const char *branch_name = nullptr;
     305         140 :         result_int = git_branch_name(&branch_name, ref.get());
     306         140 :         if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     307             : 
     308         140 :         std::string ref_name = git_reference_name(ref.get());
     309             : 
     310         140 :         branch->set_name(branch_name);
     311         140 :         branch->set_ref_name(ref_name);
     312             : 
     313         140 :         switch (git_branch_type)
     314             :         {
     315           0 :             case GIT_BRANCH_ALL: branch_type = git::BranchType::ALL; break;
     316         140 :             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         140 :         branch->set_type(branch_type);
     322             : 
     323         140 :         result_int = git_branch_is_head(ref.get());
     324         140 :         if (result_int == 1) branch->set_is_head(true);
     325             : 
     326         140 :         if (branch_type == git::BranchType::LOCAL)
     327             :         {
     328         140 :             git_buf buf_out = {nullptr};
     329         140 :             result_int = git_branch_upstream_remote(&buf_out, m_repo, ref_name.c_str());
     330         140 :             if (result_int == 0)
     331             :             {
     332         280 :                 std::string remote_name(buf_out.ptr, buf_out.ptr + buf_out.size);
     333         140 :                 branch->set_remote_name(remote_name);
     334         140 :                 git_buf_dispose(&buf_out);
     335         140 :             }
     336         140 :             result_int = git_branch_upstream_name(&buf_out, m_repo, ref_name.c_str());
     337         140 :             if (result_int == 0)
     338             :             {
     339         280 :                 std::string remote_branch(buf_out.ptr, buf_out.ptr + buf_out.size);
     340         140 :                 branch->set_remote_branch(remote_branch);
     341         140 :                 git_buf_dispose(&buf_out);
     342         140 :             }
     343             :         }
     344             :         else
     345           0 :             check_error(result, ""); //! \TODO why??
     346         420 :         branches.add(branch);
     347         411 :     }
     348             :     return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     349         131 : }
     350             : 
     351          33 : Result_t<bool> GitImpl::has_branch(const std::string &name, git::BranchType branch_type)
     352             : {
     353          33 :     Result_t<bool> result;
     354          33 :     ETRC_CALL_RET(result, name, branch_type);
     355             : 
     356          33 :     Guard<git_reference> ref;
     357          33 :     git_branch_t git_branch_type = GIT_BRANCH_ALL;
     358          33 :     Guard<git_annotated_commit> annotated_commit;
     359             :     // Guard<git_reference> branch_ref;
     360          33 :     Guard<git_reference> input_branch_ref;
     361          33 :     std::string new_ref = name;
     362             : 
     363          33 :     result = resolve_ref(input_branch_ref.get_p(), annotated_commit.get_p(), name);
     364          33 :     if (result.error())
     365             :     {
     366          31 :         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          31 :         result = find_ref(input_branch_ref.get_p(), annotated_commit.get_p(), name, new_ref);
     371          51 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT_T(result, false));
     372             :     }
     373             : 
     374          26 :     return ETRC_RET(ESYSREPO_RESULT_T(result, true));
     375          33 : }
     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          20 : Result GitImpl::get_ahead_behind(git::AheadBehind &ahead_behind, const std::string &first_ref,
     570             :                                  const std::string &second_ref)
     571             : {
     572          20 :     Result result;
     573          20 :     ETRC_CALL_RET_BEGIN(result, ahead_behind, first_ref, second_ref);
     574          20 :     ETRC_CALL_RET_OUT_END(ahead_behind);
     575             : 
     576          20 :     size_t ahead = 0;
     577          20 :     size_t behind = 0;
     578          20 :     git_oid first_ref_oid;
     579          20 :     git_oid second_ref_oid;
     580             : 
     581          20 :     ahead_behind.set_ahead(0);
     582          20 :     ahead_behind.set_behind(0);
     583             : 
     584          20 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
     585             : 
     586          20 :     result = get_commit_hash_from_branch(first_ref, first_ref_oid);
     587          20 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     588             : 
     589          20 :     result = get_commit_hash_from_branch(second_ref, second_ref_oid);
     590          20 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     591             : 
     592          20 :     int result_int = git_graph_ahead_behind(&ahead, &behind, m_repo, &first_ref_oid, &second_ref_oid);
     593          20 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR));
     594             : 
     595          20 :     ahead_behind.set_ahead(ahead);
     596          20 :     ahead_behind.set_behind(behind);
     597          40 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     598          20 : }
     599             : 
     600          37 : Result GitImpl::clone(const std::string &url, const std::string &path, const std::string &rev,
     601             :                       const git::CloneOptions &options)
     602             : {
     603          37 :     Result rresult;
     604          37 :     ETRC_CALL_RET(rresult, url, path, rev);
     605             : 
     606          37 :     int result = 0;
     607             : 
     608          37 :     self()->debug(1, "[GitImpl::clone] begin ...");
     609             : 
     610          37 :     const bool https = (url.find("https:") == 0) || (url.find("http:") == 0);
     611          37 :     const bool ssh = is_ssh_url(url);
     612             : 
     613             :     // Payload must outlive git_clone when single_branch remote_cb is set.
     614          74 :     struct CloneRemotePayload
     615             :     {
     616             :         std::string rev;
     617          37 :     } remote_payload;
     618             : 
     619          74 :     auto make_opts = [&](git_clone_options &opts) {
     620          37 :         opts = GIT_CLONE_OPTIONS_INIT;
     621             :         // Default CloneOptions: ignore rev (legacy remote-default tip only).
     622          37 :         if (options.checkout_rev && !rev.empty()) opts.checkout_branch = rev.c_str();
     623          37 :         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          74 :     };
     641             : 
     642          37 :     if (ssh)
     643             :     {
     644          27 :         self()->debug(1, "[GitImpl::clone] ssh");
     645          27 :         rresult = ensure_ssh_backend();
     646          27 :         if (rresult.error()) return ETRC_RET(check_error(rresult, false));
     647             : 
     648          27 :         self()->cmd_start();
     649          27 :         self()->open_time();
     650          27 :         m_ssh_cred_attempt = 0;
     651          27 :         m_ssh_agent_tried = false;
     652          27 :         m_ssh_explicit_tried = false;
     653             : 
     654          27 :         git_clone_options opts;
     655          27 :         make_opts(opts);
     656          27 :         setup_remote_callbacks(opts.fetch_opts.callbacks);
     657             : 
     658          27 :         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          73 :     if (result == 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     682           1 :     rresult = ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result, "Clone failed");
     683           2 :     return ETRC_RET(check_error(rresult));
     684          37 : }
     685             : 
     686          62 : Result GitImpl::checkout(const std::string &branch, bool force)
     687             : {
     688          62 :     Result result;
     689          62 :     ETRC_CALL_RET(result, branch, force);
     690             : 
     691          62 :     Guard<git_annotated_commit> annotated_commit;
     692          62 :     Guard<git_object> treeish;
     693          62 :     Guard<git_commit> target_commit;
     694          62 :     Guard<git_reference> ref;
     695          62 :     Guard<git_reference> branch_ref;
     696          62 :     Guard<git_reference> input_branch_ref;
     697          62 :     std::string new_ref = branch;
     698             : 
     699          62 :     self()->cmd_start();
     700             : 
     701          62 :     result = resolve_ref(input_branch_ref.get_p(), annotated_commit.get_p(), branch);
     702          62 :     if (result.error())
     703             :     {
     704          22 :         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          22 :         result = find_ref(input_branch_ref.get_p(), annotated_commit.get_p(), branch, new_ref);
     709          22 :         if (result.error())
     710             :         {
     711           0 :             return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     712             :         }
     713             :     }
     714             : 
     715          62 :     int result_int = git_commit_lookup(target_commit.get_p(), m_repo, git_annotated_commit_id(annotated_commit.get()));
     716          62 :     const git_oid *oid = git_commit_id(target_commit.get());
     717          62 :     std::string oid_str;
     718          62 :     result = convert_bin_hex(*oid, oid_str);
     719             : 
     720          62 :     git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
     721          62 :     if (force)
     722           0 :         opts.checkout_strategy = GIT_CHECKOUT_FORCE;
     723             :     else
     724             :         opts.checkout_strategy = GIT_CHECKOUT_SAFE;
     725             : 
     726          62 :     auto t_commit = static_cast<git_object *>(static_cast<void *>(target_commit.get()));
     727          62 :     result_int = git_checkout_tree(m_repo, t_commit, &opts);
     728          62 :     if (result_int < 0)
     729             :     {
     730           0 :         return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     731             :     }
     732             : 
     733          62 :     if (git_annotated_commit_ref(annotated_commit.get()))
     734             :     {
     735          39 :         const char *target_head = nullptr;
     736             : 
     737          39 :         result_int = git_reference_lookup(ref.get_p(), m_repo, git_annotated_commit_ref(annotated_commit.get()));
     738          39 :         if (result_int < 0)
     739             :         {
     740           0 :             return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     741             :         }
     742             : 
     743          39 :         if (git_reference_is_remote(ref.get()))
     744             :         {
     745          22 :             result_int =
     746          22 :                 git_branch_create_from_annotated(branch_ref.get_p(), m_repo, branch.c_str(), annotated_commit.get(), 0);
     747          22 :             if (result_int < 0)
     748             :             {
     749           0 :                 return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     750             :             }
     751             : 
     752          22 :             target_head = git_reference_name(branch_ref.get());
     753             : 
     754             :             // if (git_reference_is_branch(input_branch_ref.get()))
     755             :             // git_
     756          22 :             const char *branch_name = nullptr;
     757          22 :             result_int = git_branch_name(&branch_name, input_branch_ref.get());
     758          22 :             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          22 :                 result_int = git_branch_set_upstream(branch_ref.get(), branch_name);
     763          22 :                 if (result_int < 0) check_error(result, "set branch upstream"); //!  \TODO check if this is correct
     764             :             }
     765             :         }
     766             :         else
     767             :         {
     768          17 :             target_head = git_annotated_commit_ref(annotated_commit.get());
     769             :         }
     770             : 
     771          39 :         result_int = git_repository_set_head(m_repo, target_head);
     772          39 :         Result rresult;
     773             : 
     774          39 :         if (result_int < 0)
     775           0 :             rresult = ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int);
     776             :         else
     777          39 :             rresult = ESYSREPO_RESULT(ResultCode::OK);
     778          78 :         return ETRC_RET(check_error(rresult));
     779          39 :     }
     780             :     else
     781             :     {
     782          23 :         result_int = git_repository_set_head_detached_from_annotated(m_repo, annotated_commit.get());
     783          23 :         Result rresult;
     784          23 :         if (result_int < 0)
     785           0 :             rresult = ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int);
     786             :         else
     787          23 :             rresult = ESYSREPO_RESULT(ResultCode::OK);
     788             : 
     789          46 :         return ETRC_RET(check_error(rresult));
     790          23 :     }
     791          62 : }
     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          63 : Result GitImpl::get_commit_hash(const git_oid &oid_commit, git::CommitHash &commit_hash)
     872             : {
     873          63 :     Result result;
     874          63 :     ETRC_CALL_RET_BEGIN(result, commit_hash);
     875          63 :     ETRC_CALL_RET_OUT_END(commit_hash);
     876             : 
     877          63 :     std::string hash;
     878             : 
     879          63 :     result = convert_bin_hex(oid_commit, hash);
     880          63 :     if (result.ok())
     881             :     {
     882          63 :         commit_hash.set_hash(hash);
     883         126 :         return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     884             :     }
     885             : 
     886           0 :     return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     887          63 : }
     888             : 
     889          19 : Result GitImpl::get_commit(const git_oid &oid_commit, git::Commit &commit, bool get_all_notes)
     890             : {
     891          19 :     Result result;
     892          19 :     ETRC_CALL_RET_BEGIN(result, commit, get_all_notes);
     893          19 :     ETRC_CALL_RET_OUT_END(commit);
     894             : 
     895          19 :     Guard<git_commit> g_commit;
     896             : 
     897             :     // get the actual commit structure
     898          19 :     auto result_int = git_commit_lookup(g_commit.get_p(), m_repo, &oid_commit);
     899          19 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     900             : 
     901          19 :     result = get_commit_hash(oid_commit, commit.get_hash());
     902          19 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     903             : 
     904          19 :     auto message = git_commit_message(g_commit.get());
     905          38 :     if (message != nullptr) commit.set_message(message);
     906             : 
     907          19 :     auto summary = git_commit_summary(g_commit.get());
     908          38 :     if (summary != nullptr) commit.set_summary(summary);
     909             : 
     910          19 :     auto body = git_commit_body(g_commit.get());
     911          24 :     if (body != nullptr) commit.set_body(body);
     912             : 
     913          19 :     auto author = git_commit_author(g_commit.get());
     914          19 :     result = get_signature(author, commit.get_author_sign());
     915          19 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     916             : 
     917          19 :     auto committer = git_commit_committer(g_commit.get());
     918          19 :     result = get_signature(committer, commit.get_committer_sign());
     919          19 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     920             : 
     921          28 :     if (!get_all_notes) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     922             : 
     923          10 :     result = m_notes.load_all();
     924          10 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     925             : 
     926          10 :     std::vector<std::shared_ptr<git::Note>> notes;
     927             : 
     928          10 :     result = m_notes.get_note(oid_commit, notes);
     929          10 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     930             : 
     931          17 :     for (auto note : notes)
     932             :     {
     933          21 :         commit.add_note(note);
     934           7 :     }
     935             : 
     936          20 :     return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
     937          29 : }
     938             : 
     939          34 : Result GitImpl::get_last_commit(git::CommitHash &commit_hash)
     940             : {
     941          34 :     Result result;
     942          34 :     ETRC_CALL_RET_BEGIN(result, commit_hash);
     943          34 :     ETRC_CALL_RET_OUT_END(commit_hash);
     944             : 
     945          34 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
     946             : 
     947          34 :     git_oid oid_last_commit;
     948             : 
     949          34 :     self()->cmd_start();
     950             : 
     951             :     // resolve HEAD into a SHA1
     952          34 :     auto result_int = git_reference_name_to_id(&oid_last_commit, m_repo, "HEAD");
     953          34 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     954             : 
     955          34 :     result = get_commit_hash(oid_last_commit, commit_hash);
     956          68 :     return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     957          34 : }
     958             : 
     959          19 : Result GitImpl::get_last_commit(git::Commit &commit, bool get_all_notes)
     960             : {
     961          19 :     Result result;
     962          19 :     ETRC_CALL_RET_BEGIN(result, commit, get_all_notes);
     963          19 :     ETRC_CALL_RET_OUT_END(commit);
     964             : 
     965          19 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
     966             : 
     967          19 :     git_oid oid_last_commit;
     968             : 
     969          19 :     self()->cmd_start();
     970             : 
     971          19 :     commit.clear();
     972             : 
     973             :     // resolve HEAD into a SHA1
     974          19 :     auto result_int = git_reference_name_to_id(&oid_last_commit, m_repo, "HEAD");
     975          19 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
     976             : 
     977          19 :     result = get_commit(oid_last_commit, commit, get_all_notes);
     978          38 :     return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
     979          19 : }
     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          52 : Result GitImpl::get_signature(const git_signature *signature_obj, git::Signature &signature)
     991             : {
     992          52 :     signature.set_name(signature_obj->name);
     993          52 :     signature.set_email(signature_obj->email);
     994             : 
     995          52 :     git_time_t commit_time = signature_obj->when.time;
     996             : 
     997          52 :     std::time_t commit_time_t = static_cast<std::time_t>(commit_time);
     998          52 :     std::string commit_time_str = std::asctime(std::localtime(&commit_time_t));
     999             : 
    1000          52 :     signature.set_date_time(std::chrono::system_clock::from_time_t(commit_time_t));
    1001          52 :     signature.set_offset(signature_obj->when.offset);
    1002             : 
    1003          52 :     return ESYSREPO_RESULT(ResultCode::OK);
    1004          52 : }
    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          42 : Result GitImpl::is_dirty(bool &dirty)
    1066             : {
    1067          42 :     Result result;
    1068          42 :     ETRC_CALL_RET_BEGIN(result, dirty);
    1069          42 :     ETRC_CALL_RET_OUT_END(dirty);
    1070             : 
    1071          42 :     if (m_repo == nullptr) return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
    1072             : 
    1073          42 :     Guard<git_status_list> status;
    1074          42 :     git_status_options statusopt = GIT_STATUS_OPTIONS_INIT;
    1075             : 
    1076          42 :     self()->cmd_start();
    1077             : 
    1078          42 :     statusopt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
    1079          42 :     statusopt.flags = GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
    1080             : 
    1081          42 :     int result_int = git_status_list_new(status.get_p(), m_repo, &statusopt);
    1082          42 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
    1083             : 
    1084          42 :     std::size_t count = git_status_list_entrycount(status.get());
    1085          42 :     dirty = (count != 0);
    1086          84 :     return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
    1087          42 : }
    1088             : 
    1089          54 : Result GitImpl::is_detached(bool &detached)
    1090             : {
    1091          54 :     Result result;
    1092          54 :     ETRC_CALL_RET_BEGIN(result, detached);
    1093          54 :     ETRC_CALL_RET_OUT_END(detached);
    1094             : 
    1095          54 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    1096             : 
    1097          54 :     int result_int = git_repository_head_detached(m_repo);
    1098          54 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    1099             : 
    1100          54 :     if (result_int == 1)
    1101          11 :         detached = true;
    1102             :     else
    1103          43 :         detached = false;
    1104         108 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    1105          54 : }
    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         507 : Result GitImpl::check_error(Result result, bool show_result)
    1299             : {
    1300         507 :     self()->cmd_end();
    1301             : 
    1302         507 :     if (result.ok()) return result;
    1303             : 
    1304           2 :     const git_error *error = git_error_last();
    1305             : 
    1306           2 :     std::ostringstream oss;
    1307             :     // oss << "ERROR " << result << " : " << action;
    1308           6 :     if (result.get_error_info() != nullptr) oss << result.get_error_info()->get_text();
    1309           2 :     if (show_result)
    1310             :     {
    1311           2 :         oss << " (" << result.get_result_code_int() << ").";
    1312           2 :         if (error && error->message)
    1313             :         {
    1314           2 :             oss << " - " << error->message;
    1315             :         }
    1316             :     }
    1317           2 :     self()->error(oss.str());
    1318             : 
    1319           2 :     return result;
    1320           2 : }
    1321             : 
    1322        2881 : Git *GitImpl::self() const
    1323             : {
    1324        2881 :     return m_self;
    1325             : }
    1326             : 
    1327          65 : int GitImpl::libgit2_credentials_cb(git_credential **out, const char *url, const char *name, unsigned int types,
    1328             :                                     void *payload)
    1329             : {
    1330          65 :     (void)url;
    1331             : 
    1332          65 :     const git_error *error = git_error_last();
    1333          65 :     if (error && error->klass == GIT_ERROR_SSH) return -1;
    1334             : 
    1335          64 :     auto self = static_cast<GitImpl *>(payload);
    1336          64 :     self->m_ssh_cred_attempt++;
    1337             : 
    1338          64 :     const char *username = (name != nullptr && name[0] != '\0') ? name : "git";
    1339             : 
    1340          64 :     Result_t<git::SshBackend> backend = GitBase::get_ssh_backend();
    1341          64 :     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          64 :     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          64 :     if ((types & GIT_CREDENTIAL_SSH_KEY) || (types & GIT_CREDENTIAL_SSH_MEMORY))
    1354             :     {
    1355          64 :         if (!self->m_ssh_explicit_tried)
    1356             :         {
    1357          63 :             self->m_ssh_explicit_tried = true;
    1358          63 :             std::string key_path;
    1359          63 :             const int explicit_result = try_explicit_ssh_key(out, username, &key_path);
    1360          63 :             if (explicit_result == 0)
    1361             :             {
    1362          63 :                 log_ssh_auth_info_once(self->self(),
    1363         126 :                                        "SSH credentials: libssh2 explicit identity '" + key_path + "'");
    1364          63 :                 return 0;
    1365             :             }
    1366          63 :         }
    1367             : 
    1368           2 :         if (!self->m_ssh_agent_tried && self->is_ssh_agent_running())
    1369             :         {
    1370           1 :             self->m_ssh_agent_tried = true;
    1371           1 :             self->self()->debug(0, "[GitImpl::libgit2_credentials_cb] using ssh agent");
    1372             : 
    1373           1 :             std::string agent_id_path = libssh2::SSH::get_dflt_agent_identity_path();
    1374           1 :             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           1 :             std::ostringstream oss;
    1381           1 :             oss << "[GitImpl::libgit2_credentials_cb] use custom agent with path '" << agent_id_path << "'";
    1382           1 :             self->self()->debug(0, oss.str());
    1383           1 :             log_ssh_auth_info_once(self->self(),
    1384           2 :                                    "SSH credentials: libssh2 via custom ssh-agent ('" + agent_id_path + "')");
    1385           1 :             return git_credential_ssh_key_from_custom_agent(out, username, agent_id_path.c_str());
    1386           2 :         }
    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          64 :     }
    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          65 : }
    1406             : 
    1407          37 : bool GitImpl::is_ssh_url(const std::string &url)
    1408             : {
    1409          37 :     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          69 : void GitImpl::setup_remote_callbacks(git_remote_callbacks &callbacks)
    1420             : {
    1421          69 :     callbacks.sideband_progress = &GitImpl::libgit2_sideband_progress_cb;
    1422          69 :     callbacks.credentials = &GitImpl::libgit2_credentials_cb;
    1423          69 :     callbacks.transfer_progress = &GitImpl::libgit2_transfer_progress_cb;
    1424          69 :     callbacks.update_tips = &GitImpl::libgit2_update_tips_cb;
    1425          69 :     callbacks.pack_progress = &GitImpl::libgit2_pack_progress_cb;
    1426          69 :     callbacks.payload = this;
    1427          69 : }
    1428             : 
    1429          69 : Result GitImpl::ensure_ssh_backend()
    1430             : {
    1431          69 :     static std::mutex mutex;
    1432          69 :     static bool done = false;
    1433          69 :     static Result cached = Result::OK;
    1434             : 
    1435          69 :     std::lock_guard<std::mutex> lock(mutex);
    1436          69 :     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          69 : }
    1505             : 
    1506          64 : void GitImpl::log_ssh_auth_info_once(Git *self, const std::string &msg)
    1507             : {
    1508          64 :     static std::mutex mutex;
    1509          64 :     static bool logged = false;
    1510             : 
    1511          64 :     std::lock_guard<std::mutex> lock(mutex);
    1512          64 :     if (logged) return;
    1513           1 :     logged = true;
    1514           1 :     if (self != nullptr) self->info(msg);
    1515          64 : }
    1516             : 
    1517          63 : int GitImpl::try_explicit_ssh_key(git_credential **out, const char *username, std::string *used_private_key)
    1518             : {
    1519          63 :     const std::string private_str = git::explicit_ssh_private_key_from_env();
    1520          63 :     if (private_str.empty()) return -1;
    1521             : 
    1522          63 :     boost::filesystem::path private_key(private_str);
    1523          63 :     boost::filesystem::path public_key = private_key;
    1524          63 :     public_key += ".pub";
    1525          63 :     const std::string public_str = public_key.string();
    1526          63 :     const char *public_path = boost::filesystem::exists(public_key) ? public_str.c_str() : nullptr;
    1527             : 
    1528          63 :     if (used_private_key != nullptr) *used_private_key = private_str;
    1529             : 
    1530          63 :     return git_credential_ssh_key_new(out, username, public_path, private_str.c_str(), "");
    1531         126 : }
    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         185 : int GitImpl::libgit2_sideband_progress_cb(const char *str, int len, void *data)
    1587             : {
    1588         185 :     auto impl = static_cast<GitImpl *>(data);
    1589             : 
    1590         185 :     if (impl == nullptr) return 0;
    1591             : 
    1592         185 :     std::string txt(str, str + len);
    1593             : 
    1594         185 :     int result = impl->self()->handle_sideband_progress(txt);
    1595         185 :     return result;
    1596         185 : }
    1597             : 
    1598         645 : int GitImpl::libgit2_transfer_progress_cb(const git_indexer_progress *stats, void *payload)
    1599             : {
    1600         645 :     auto impl = static_cast<GitImpl *>(payload);
    1601             : 
    1602         645 :     if (impl == nullptr) return 0;
    1603             : 
    1604         645 :     git::Progress progress;
    1605             : 
    1606             :     // std::cout << "[libgit2_transfer_progress_cb]" << std::endl;
    1607             : 
    1608         645 :     progress.set_total_objects(static_cast<int>(stats->total_objects));
    1609         645 :     progress.set_received_objects(static_cast<int>(stats->received_objects));
    1610         645 :     progress.set_indexed_objects(static_cast<int>(stats->indexed_objects));
    1611         645 :     progress.set_total_deltas(static_cast<int>(stats->total_deltas));
    1612         645 :     progress.set_indexed_deltas(static_cast<int>(stats->indexed_deltas));
    1613         645 :     progress.set_received_bytes(static_cast<std::int64_t>(stats->received_bytes));
    1614             : 
    1615         645 :     if (stats->received_objects == stats->total_objects)
    1616             :     {
    1617         143 :         progress.set_fetch_step(git::FetchStep::RESOLVING);
    1618             : 
    1619         143 :         if (stats->indexed_deltas == stats->total_deltas)
    1620             :         {
    1621          85 :             progress.set_done(true);
    1622          85 :             progress.set_percentage(git::Progress::MAX_PERCENTAGE);
    1623             :         }
    1624             :         else
    1625             :         {
    1626          58 :             double percentage = (100.0 * stats->indexed_deltas) / stats->total_deltas;
    1627             : 
    1628          58 :             progress.set_done(false);
    1629          58 :             progress.set_percentage(static_cast<int>(percentage));
    1630             :         }
    1631             :     }
    1632         502 :     else if (stats->total_objects > 0)
    1633             :     {
    1634         502 :         progress.set_fetch_step(git::FetchStep::RECEIVING);
    1635         502 :         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         502 :             double percentage = (100.0 * stats->received_objects) / stats->total_objects;
    1643             : 
    1644         502 :             progress.set_done(false);
    1645         502 :             progress.set_percentage(static_cast<int>(percentage));
    1646             :         }
    1647             :     }
    1648             : 
    1649         645 :     impl->self()->notify_progress_throttled(progress);
    1650             : 
    1651             :     return 0;
    1652             : }
    1653             : 
    1654         114 : int GitImpl::libgit2_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data)
    1655             : {
    1656         114 :     std::string a_str;
    1657         114 :     std::string b_str;
    1658         114 :     auto self = static_cast<GitImpl *>(data);
    1659         114 :     std::ostringstream oss;
    1660         114 :     git::UpdateTip update_tip;
    1661             : 
    1662         114 :     auto result = self->convert_bin_hex(*b, b_str);
    1663         114 :     if (result.error()) return -1;
    1664             : 
    1665         114 :     update_tip.set_ref_name(refname);
    1666             : 
    1667         114 :     if (git_oid_is_zero(a))
    1668             :     {
    1669             :         // oss << "[new]     " << b_str << " " << refname;
    1670             :         // std::cout << oss.str() << std::endl;
    1671         114 :         update_tip.set_type(git::UpdateTipType::NEW);
    1672         114 :         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         114 : }
    1687             : 
    1688          12 : int GitImpl::libgit2_pack_progress_cb(int /*stage*/, uint32_t /*current*/, uint32_t /*total*/, void * /*payload*/)
    1689             : {
    1690          12 :     return 0;
    1691             : }
    1692             : 
    1693           2 : Result_t<bool> GitImpl::is_ssh_agent_running()
    1694             : {
    1695           2 :     bool present = m_ssh.is_agent_present();
    1696             : 
    1697           4 :     if (present) self()->debug(0, "SSH agent detected");
    1698             : 
    1699           2 :     return present;
    1700             : }
    1701             : 
    1702          27 : Result GitImpl::merge_analysis(const std::vector<std::string> &refs, git::MergeAnalysisResult &merge_analysis_result,
    1703             :                                std::vector<git::CommitHash> &commits)
    1704             : {
    1705          27 :     Result result;
    1706          27 :     ETRC_CALL_RET_BEGIN(result, refs, merge_analysis_result, commits);
    1707          27 :     ETRC_CALL_RET_OUT_END(merge_analysis_result, commits);
    1708             : 
    1709          27 :     git_repository_state_t state = GIT_REPOSITORY_STATE_NONE;
    1710          27 :     git_merge_analysis_t analysis = GIT_MERGE_ANALYSIS_NONE;
    1711          27 :     git_merge_preference_t preference = GIT_MERGE_PREFERENCE_NONE;
    1712          27 :     git_annotated_commit *annotated = nullptr;
    1713          27 :     git::CommitHash commit;
    1714             : 
    1715          27 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    1716             : 
    1717          27 :     state = static_cast<git_repository_state_t>(git_repository_state(m_repo));
    1718          27 :     if (state != GIT_REPOSITORY_STATE_NONE)
    1719             :     {
    1720           0 :         return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    1721             :     }
    1722             : 
    1723          27 :     std::vector<git_annotated_commit *> annotated_vec;
    1724          27 :     std::vector<git_oid> oids;
    1725          27 :     const git_oid *target_oid = nullptr;
    1726          27 :     std::string hash;
    1727             : 
    1728          54 :     for (auto &ref : refs)
    1729             :     {
    1730          27 :         result = resolve_ref(&annotated, ref);
    1731          27 :         if (result.ok())
    1732             :         {
    1733          27 :             target_oid = git_annotated_commit_id(annotated);
    1734          27 :             oids.push_back(*target_oid);
    1735          27 :             annotated_vec.push_back(annotated);
    1736             : 
    1737          27 :             result = convert_bin_hex(*target_oid, hash);
    1738          27 :             if (result.ok())
    1739             :             {
    1740          27 :                 commit.set_hash(hash);
    1741          27 :                 commits.push_back(commit);
    1742             :             }
    1743             :         }
    1744             :     }
    1745             : 
    1746          27 :     int result_int = git_merge_analysis(&analysis, &preference, m_repo,
    1747          27 :                                         (const git_annotated_commit **)annotated_vec.data(), annotated_vec.size());
    1748             :     //! \TODO handling error is missing
    1749          54 :     for (auto annotated : annotated_vec) git_annotated_commit_free(annotated);
    1750             : 
    1751          27 :     if (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE)
    1752          25 :         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          54 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    1767          54 : }
    1768             : 
    1769          56 : Result GitImpl::find_remote(Guard<git_remote> &remote, const std::string &remote_name)
    1770             : {
    1771          56 :     Result result;
    1772          56 :     ETRC_CALL_RET_BEGIN(result, remote, remote_name);
    1773          56 :     ETRC_CALL_RET_OUT_END(remote);
    1774             : 
    1775          56 :     if (m_repo == nullptr) return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
    1776             : 
    1777          56 :     std::string remote_name_str;
    1778             : 
    1779          56 :     if (!remote_name.empty())
    1780             :     {
    1781          19 :         int result_int = git_remote_lookup(remote.get_p(), m_repo, remote_name.c_str());
    1782          19 :         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          19 :         remote_name_str = git_remote_name(remote.get());
    1791             :     }
    1792             :     else
    1793             :     {
    1794          37 :         git::Branches branches;
    1795             : 
    1796          37 :         result = get_branches(branches);
    1797          37 :         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          37 :     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          37 :         branches.sort();
    1815             : 
    1816          37 :         remote_name_str = branches.get()[0]->get_remote_name();
    1817             : 
    1818          37 :         int result_int = git_remote_lookup(remote.get_p(), m_repo, remote_name_str.c_str());
    1819          37 :         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          37 :     }
    1827             : 
    1828         112 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    1829          56 : }
    1830             : 
    1831          40 : Result GitImpl::get_commit_hash_from_branch(const std::string &branch, git_oid &oid)
    1832             : {
    1833          40 :     Guard<git_annotated_commit> annotated_commit;
    1834          40 :     Guard<git_reference> input_branch_ref;
    1835          40 :     std::string new_ref = branch;
    1836          40 :     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          40 :     Result result = resolve_ref(input_branch_ref.get_p(), annotated_commit.get_p(), branch);
    1844          40 :     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          40 :     int result_int = git_commit_lookup(target_commit.get_p(), m_repo, git_annotated_commit_id(annotated_commit.get()));
    1858          40 :     if (result_int < 0) return check_error(ESYSREPO_RESULT(ResultCode::GIT_FIND_REF_FAILED));
    1859             : 
    1860          40 :     const git_oid *target_oid = git_commit_id(target_commit.get());
    1861          40 :     if (target_oid == nullptr) ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR);
    1862          40 :     oid = *target_oid;
    1863          40 :     return ESYSREPO_RESULT(ResultCode::OK);
    1864          40 : }
    1865             : 
    1866          36 : Result GitImpl::fetch(const std::string &remote_str)
    1867             : {
    1868          36 :     Result result;
    1869          36 :     ETRC_CALL_RET(result, remote_str);
    1870             : 
    1871          36 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    1872             : 
    1873          36 :     result = ensure_ssh_backend();
    1874          36 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    1875             : 
    1876          36 :     Guard<git_remote> remote;
    1877             : 
    1878          36 :     result = find_remote(remote, remote_str);
    1879          36 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    1880             : 
    1881          36 :     git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
    1882          36 :     m_ssh_cred_attempt = 0;
    1883          36 :     m_ssh_agent_tried = false;
    1884          36 :     m_ssh_explicit_tried = false;
    1885          36 :     setup_remote_callbacks(fetch_opts.callbacks);
    1886             : 
    1887          36 :     int result_int = git_remote_fetch(remote.get(), nullptr, &fetch_opts, "fetch");
    1888          36 :     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          36 :     const git_indexer_progress *stats = git_remote_stats(remote.get());
    1896             : 
    1897             :     //! \TODO what to do with the stats;
    1898             : 
    1899          72 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    1900          36 : }
    1901             : 
    1902           0 : Result GitImpl::fetch_refspec(const std::string &refspec, const std::string &remote_str)
    1903             : {
    1904           0 :     Result result;
    1905           0 :     ETRC_CALL_RET(result, refspec, remote_str);
    1906             : 
    1907           0 :     if (refspec.empty()) return ETRC_RET(fetch(remote_str));
    1908           0 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    1909             : 
    1910           0 :     result = ensure_ssh_backend();
    1911           0 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    1912             : 
    1913           0 :     Guard<git_remote> remote;
    1914           0 :     result = find_remote(remote, remote_str);
    1915           0 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    1916             : 
    1917           0 :     git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
    1918           0 :     m_ssh_cred_attempt = 0;
    1919           0 :     m_ssh_agent_tried = false;
    1920           0 :     m_ssh_explicit_tried = false;
    1921           0 :     setup_remote_callbacks(fetch_opts.callbacks);
    1922             : 
    1923           0 :     std::string spec = refspec;
    1924           0 :     char *strings[] = {spec.data()};
    1925           0 :     git_strarray specs = {strings, 1};
    1926             : 
    1927           0 :     int result_int = git_remote_fetch(remote.get(), &specs, &fetch_opts, "fetch");
    1928           0 :     if (result_int < 0)
    1929             :     {
    1930           0 :         std::string err_str = "Fetch refspec failed";
    1931           0 :         self()->error(err_str);
    1932           0 :         return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, err_str));
    1933           0 :     }
    1934             : 
    1935           0 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    1936           0 : }
    1937             : 
    1938             : namespace
    1939             : {
    1940             : 
    1941           0 : struct CollectSubmodules
    1942             : {
    1943             :     std::vector<std::string> names;
    1944             : };
    1945             : 
    1946           0 : int collect_submodule_cb(git_submodule * /*sm*/, const char *name, void *payload)
    1947             : {
    1948           0 :     auto *data = static_cast<CollectSubmodules *>(payload);
    1949           0 :     if (name != nullptr) data->names.emplace_back(name);
    1950           0 :     return 0;
    1951             : }
    1952             : 
    1953             : } // namespace
    1954             : 
    1955           0 : Result GitImpl::update_submodules(const std::string &path, bool recursive)
    1956             : {
    1957           0 :     Result result;
    1958           0 :     ETRC_CALL_RET(result, path, recursive);
    1959             : 
    1960           0 :     if (path.empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty path"));
    1961             : 
    1962           0 :     git_repository *repo = nullptr;
    1963           0 :     int result_int = git_repository_open(&repo, path.c_str());
    1964           0 :     if (result_int < 0)
    1965           0 :         return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "failed to open repo for submodules"));
    1966             : 
    1967           0 :     CollectSubmodules collected;
    1968           0 :     result_int = git_submodule_foreach(repo, collect_submodule_cb, &collected);
    1969           0 :     if (result_int < 0)
    1970             :     {
    1971           0 :         git_repository_free(repo);
    1972           0 :         return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "git_submodule_foreach failed"));
    1973             :     }
    1974             : 
    1975           0 :     git_submodule_update_options update_opts = GIT_SUBMODULE_UPDATE_OPTIONS_INIT;
    1976           0 :     result = ensure_ssh_backend();
    1977           0 :     if (result.error())
    1978             :     {
    1979           0 :         git_repository_free(repo);
    1980           0 :         return ETRC_RET(ESYSREPO_RESULT(result));
    1981             :     }
    1982           0 :     m_ssh_cred_attempt = 0;
    1983           0 :     m_ssh_agent_tried = false;
    1984           0 :     m_ssh_explicit_tried = false;
    1985           0 :     setup_remote_callbacks(update_opts.fetch_opts.callbacks);
    1986             : 
    1987           0 :     for (const auto &name : collected.names)
    1988             :     {
    1989           0 :         git_submodule *sm = nullptr;
    1990           0 :         result_int = git_submodule_lookup(&sm, repo, name.c_str());
    1991           0 :         if (result_int < 0)
    1992             :         {
    1993           0 :             git_repository_free(repo);
    1994           0 :             return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "git_submodule_lookup failed"));
    1995             :         }
    1996             : 
    1997           0 :         result_int = git_submodule_update(sm, 1 /* init */, &update_opts);
    1998           0 :         if (result_int < 0)
    1999             :         {
    2000           0 :             git_submodule_free(sm);
    2001           0 :             git_repository_free(repo);
    2002           0 :             return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "git_submodule_update failed"));
    2003             :         }
    2004             : 
    2005           0 :         if (recursive)
    2006             :         {
    2007           0 :             const char *rel = git_submodule_path(sm);
    2008           0 :             boost::filesystem::path child = path;
    2009           0 :             if (rel != nullptr) child /= rel;
    2010           0 :             git_submodule_free(sm);
    2011           0 :             sm = nullptr;
    2012           0 :             result = update_submodules(child.generic_string(), true);
    2013           0 :             if (result.error())
    2014             :             {
    2015           0 :                 git_repository_free(repo);
    2016           0 :                 return ETRC_RET(ESYSREPO_RESULT(result));
    2017             :             }
    2018           0 :         }
    2019             :         else
    2020             :         {
    2021           0 :             git_submodule_free(sm);
    2022             :         }
    2023             :     }
    2024             : 
    2025           0 :     git_repository_free(repo);
    2026           0 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    2027           0 : }
    2028             : 
    2029           3 : Result GitImpl::fetch_all_notes(const std::string &remote_str)
    2030             : {
    2031           3 :     Result result;
    2032           3 :     ETRC_CALL_RET(result, remote_str);
    2033             : 
    2034           3 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    2035             : 
    2036           3 :     result = ensure_ssh_backend();
    2037           3 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    2038             : 
    2039           3 :     m_notes.clear();
    2040             : 
    2041           3 :     Guard<git_remote> remote;
    2042             : 
    2043           3 :     result = find_remote(remote, remote_str);
    2044           3 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    2045             : 
    2046           3 :     Guard<git_refspec> ref_spec;
    2047           3 :     int result_int = git_refspec_parse(ref_spec.get_p(), "refs/notes/*:refs/notes/*", 1);
    2048           3 :     if (result_int != 0) ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
    2049             : 
    2050           3 :     git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
    2051           3 :     m_ssh_cred_attempt = 0;
    2052           3 :     m_ssh_agent_tried = false;
    2053           3 :     m_ssh_explicit_tried = false;
    2054           3 :     setup_remote_callbacks(fetch_opts.callbacks);
    2055             : 
    2056           3 :     git_strarray refspecs;
    2057           3 :     char refspecs_str[] = "refs/notes/*:refs/notes/*";
    2058           3 :     char *refspecs_array[1];
    2059           3 :     refspecs_array[0] = refspecs_str;
    2060           3 :     refspecs.count = 1;
    2061           3 :     refspecs.strings = refspecs_array;
    2062             : 
    2063           3 :     result_int = git_remote_fetch(remote.get(), &refspecs, &fetch_opts, "fetch");
    2064           3 :     if (result_int < 0)
    2065             :     {
    2066           0 :         std::string err_str = "Fetch failed";
    2067           0 :         self()->error(err_str);
    2068           0 :         return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, err_str));
    2069           0 :     }
    2070             : 
    2071           6 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    2072           6 : }
    2073             : 
    2074           5 : Result GitImpl::add_note(git::NoteId &note_id, const std::string &reference, const std::string &message, bool overwrite)
    2075             : {
    2076           5 :     Result result;
    2077           5 :     ETRC_CALL_RET_BEGIN(result, note_id, reference, message, overwrite);
    2078           5 :     ETRC_CALL_RET_OUT_END(note_id);
    2079             : 
    2080           5 :     if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
    2081           5 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    2082          10 :     if (self()->get_author() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::NO_AUTHOR_GIVEN));
    2083             : 
    2084           5 :     std::string the_ref = reference;
    2085           5 :     Guard<git_signature> author_sig;
    2086           5 :     Guard<git_signature> committer_sig;
    2087             : 
    2088           5 :     the_ref = normalize_notes_ref(reference);
    2089             : 
    2090           5 :     result = get_sigs(author_sig.get_p(), committer_sig.get_p());
    2091           5 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
    2092             : 
    2093           5 :     git_oid note_iod;
    2094           5 :     git_oid oid_last_commit;
    2095             : 
    2096             :     // resolve HEAD into a SHA1
    2097           5 :     auto result_int = git_reference_name_to_id(&oid_last_commit, m_repo, "HEAD");
    2098           5 :     if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
    2099             : 
    2100           5 :     result_int = git_note_create(&note_iod, m_repo, the_ref.c_str(), author_sig.get(), committer_sig.get(),
    2101             :                                  &oid_last_commit, message.c_str(), overwrite);
    2102           5 :     if (result_int != 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
    2103             : 
    2104           5 :     m_notes.clear();
    2105             : 
    2106           5 :     result = get_commit_hash(note_iod, note_id);
    2107           5 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
    2108             : 
    2109           5 :     result = get_commit_hash(oid_last_commit, note_id.get_commit_hash());
    2110           5 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
    2111             : 
    2112           5 :     note_id.set_reference(reference);
    2113             : 
    2114          10 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    2115          10 : }
    2116             : 
    2117           1 : Result GitImpl::remove_note(const git::NoteId &note_id)
    2118             : {
    2119           1 :     Result result;
    2120           1 :     ETRC_CALL_RET(result, note_id);
    2121             : 
    2122           1 :     if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
    2123           1 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    2124           2 :     if (self()->get_author() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::NO_AUTHOR_GIVEN));
    2125             : 
    2126           1 :     git_oid note_oid;
    2127             : 
    2128           1 :     int result_int = git_oid_fromstr(&note_oid, note_id.get_hash().c_str());
    2129           1 :     if (result_int != 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
    2130             : 
    2131           2 :     return ETRC_RET(remove_note(note_id.get_commit_hash(), note_id.get_reference()));
    2132           1 : }
    2133             : 
    2134           2 : Result GitImpl::remove_note(const git::CommitHash &commit_hash, const std::string &reference)
    2135             : {
    2136           2 :     Result result;
    2137           2 :     ETRC_CALL_RET(result, commit_hash, reference);
    2138             : 
    2139           2 :     if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
    2140           2 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    2141           4 :     if (self()->get_author() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::NO_AUTHOR_GIVEN));
    2142             : 
    2143           2 :     Guard<git_signature> author_sig;
    2144           2 :     Guard<git_signature> committer_sig;
    2145           2 :     git_oid target_oid;
    2146             : 
    2147           2 :     result = get_sigs(author_sig.get_p(), committer_sig.get_p());
    2148           2 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
    2149             : 
    2150           2 :     int result_int = git_oid_fromstr(&target_oid, commit_hash.get_hash().c_str());
    2151           2 :     if (result_int != 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
    2152             : 
    2153           2 :     std::string note_ref = normalize_notes_ref(reference);
    2154           2 :     result_int = git_note_remove(m_repo, note_ref.c_str(), author_sig.get(), committer_sig.get(), &target_oid);
    2155             : 
    2156           2 :     if (result_int != 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
    2157             : 
    2158           2 :     m_notes.clear();
    2159           4 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    2160           4 : }
    2161             : 
    2162           1 : Result GitImpl::remove_note_last_commit(const std::string &reference)
    2163             : {
    2164           1 :     Result result;
    2165           1 :     ETRC_CALL_RET(result, reference);
    2166             : 
    2167           1 :     git::CommitHash commit_hash;
    2168           1 :     result = get_last_commit(commit_hash);
    2169           1 :     if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
    2170             : 
    2171           2 :     return ETRC_RET(remove_note(commit_hash, reference));
    2172           1 : }
    2173             : 
    2174           2 : Result GitImpl::get_refspec(std::string &refspec_str, const std::string &src_ref, const std::string &dst_ref)
    2175             : {
    2176           2 :     Result result;
    2177           2 :     ETRC_CALL_RET_BEGIN(result, refspec_str, src_ref, dst_ref);
    2178           2 :     ETRC_CALL_RET_OUT_END(refspec_str);
    2179             : 
    2180           2 :     if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
    2181           2 :     if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
    2182             : 
    2183           2 :     std::string src_ref_name;
    2184           2 :     std::string dst_ref_name;
    2185             : 
    2186           2 :     if (src_ref.empty() && dst_ref.empty())
    2187             :     {
    2188             :         // Push the head
    2189           0 :         Guard<git_reference> head_ref;
    2190             : 
    2191           0 :         int result_int = git_repository_head(head_ref.get_p(), m_repo);
    2192           0 :         if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    2193             : 
    2194           0 :         src_ref_name = git_reference_name(head_ref.get());
    2195           0 :     }
    2196           2 :     if (!src_ref.empty())
    2197             :     {
    2198           2 :         Guard<git_reference> git_src_ref;
    2199           2 :         result = resolve_ref(git_src_ref.get_p(), src_ref);
    2200           2 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    2201             : 
    2202           2 :         src_ref_name = git_reference_name(git_src_ref.get());
    2203           2 :     }
    2204           2 :     if (!dst_ref.empty())
    2205             :     {
    2206           0 :         Guard<git_reference> git_dst_ref;
    2207           0 :         result = resolve_ref(git_dst_ref.get_p(), src_ref);
    2208           0 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    2209             : 
    2210           0 :         dst_ref_name = git_reference_name(git_dst_ref.get());
    2211           0 :     }
    2212             :     else
    2213           2 :         dst_ref_name = src_ref_name;
    2214             : 
    2215           2 :     refspec_str = src_ref_name;
    2216           2 :     refspec_str += ":";
    2217           2 :     refspec_str += dst_ref_name;
    2218           4 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    2219           4 : }
    2220             : 
    2221           2 : Result GitImpl::push(const std::string &remote, const std::string &src_ref, const std::string &dst_ref)
    2222             : {
    2223           2 :     Result result;
    2224           2 :     ETRC_CALL_RET(result, src_ref, dst_ref);
    2225             : 
    2226           2 :     result = ensure_ssh_backend();
    2227           2 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    2228             : 
    2229           2 :     git_push_options options;
    2230           2 :     Guard<git_remote> remote_git;
    2231           2 :     std::string refspec_str;
    2232             : 
    2233           2 :     result = get_refspec(refspec_str, src_ref, dst_ref);
    2234           2 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    2235             : 
    2236           2 :     char *refspec = (char *)refspec_str.c_str();
    2237           2 :     const git_strarray refspecs = {&refspec, 1};
    2238             : 
    2239           2 :     int result_int = git_remote_lookup(remote_git.get_p(), m_repo, remote.c_str());
    2240           2 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    2241             : 
    2242           2 :     result_int = git_push_options_init(&options, GIT_PUSH_OPTIONS_VERSION);
    2243           2 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    2244             : 
    2245           2 :     m_ssh_cred_attempt = 0;
    2246           2 :     m_ssh_agent_tried = false;
    2247           2 :     m_ssh_explicit_tried = false;
    2248           2 :     setup_remote_callbacks(options.callbacks);
    2249             : 
    2250           2 :     result_int = git_remote_push(remote_git.get(), &refspecs, &options);
    2251           2 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    2252             : 
    2253           4 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    2254           4 : }
    2255             : 
    2256           1 : Result GitImpl::push_notes(const std::string &remote, const std::string &reference)
    2257             : {
    2258           1 :     Result result;
    2259           1 :     ETRC_CALL_RET(result, remote, reference);
    2260             : 
    2261           1 :     result = ensure_ssh_backend();
    2262           1 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
    2263             : 
    2264           1 :     git_push_options options;
    2265           1 :     Guard<git_remote> remote_git;
    2266           1 :     std::string note_ref = normalize_notes_ref(reference);
    2267           1 :     char *refspec = (char *)note_ref.c_str();
    2268           1 :     const git_strarray refspecs = {&refspec, 1};
    2269             : 
    2270           1 :     int result_int = git_remote_lookup(remote_git.get_p(), m_repo, remote.c_str());
    2271           1 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    2272             : 
    2273           1 :     result_int = git_push_options_init(&options, GIT_PUSH_OPTIONS_VERSION);
    2274           1 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    2275             : 
    2276           1 :     m_ssh_cred_attempt = 0;
    2277           1 :     m_ssh_agent_tried = false;
    2278           1 :     m_ssh_explicit_tried = false;
    2279           1 :     setup_remote_callbacks(options.callbacks);
    2280             : 
    2281           1 :     result_int = git_remote_push(remote_git.get(), &refspecs, &options);
    2282           1 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
    2283             : 
    2284           2 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
    2285           2 : }
    2286             : 
    2287           2 : Result GitImpl::resolve_ref(git_reference **ref, const std::string &ref_str)
    2288             : {
    2289           2 :     Guard<git_object> git_obj;
    2290           2 :     int result = 0;
    2291             : 
    2292           2 :     assert(ref != nullptr);
    2293           2 :     assert(m_repo != nullptr);
    2294             : 
    2295           2 :     result = git_reference_dwim(ref, m_repo, ref_str.c_str());
    2296           2 :     if (result == GIT_OK) return ESYSREPO_RESULT(ResultCode::OK);
    2297             : 
    2298           0 :     result = git_revparse_single(git_obj.get_p(), m_repo, ref_str.c_str());
    2299           0 :     if (result == GIT_OK) return ESYSREPO_RESULT(ResultCode::OK);
    2300             : 
    2301           0 :     return ESYSREPO_RESULT(ResultCode::GENERIC_ERROR);
    2302           2 : }
    2303             : 
    2304          27 : Result GitImpl::resolve_ref(git_annotated_commit **commit, const std::string &ref)
    2305             : {
    2306          27 :     git_reference *git_ref = nullptr;
    2307          27 :     git_object *git_obj = nullptr;
    2308          27 :     int result = 0;
    2309             : 
    2310          27 :     assert(commit != nullptr);
    2311          27 :     assert(m_repo != nullptr);
    2312             : 
    2313          27 :     result = git_reference_dwim(&git_ref, m_repo, ref.c_str());
    2314          27 :     if (result == GIT_OK)
    2315             :     {
    2316          27 :         const char *name = git_reference_name(git_ref);
    2317             : 
    2318             :         //! \TODO remote this
    2319          27 :         const char *branch_name = nullptr;
    2320          27 :         git_branch_name(&branch_name, git_ref);
    2321             : 
    2322          27 :         git_annotated_commit_from_ref(commit, m_repo, git_ref);
    2323          27 :         git_reference_free(git_ref);
    2324          27 :         return ESYSREPO_RESULT(ResultCode::OK);
    2325             :     }
    2326             : 
    2327           0 :     result = git_revparse_single(&git_obj, m_repo, ref.c_str());
    2328           0 :     if (result == GIT_OK)
    2329             :     {
    2330           0 :         result = git_annotated_commit_lookup(commit, m_repo, git_object_id(git_obj));
    2331           0 :         git_object_free(git_obj);
    2332           0 :         return ESYSREPO_RESULT(ResultCode::OK);
    2333             :     }
    2334             : 
    2335           0 :     return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
    2336             : }
    2337             : 
    2338         217 : Result GitImpl::resolve_ref(git_reference **ref, git_annotated_commit **commit, const std::string &ref_str)
    2339             : {
    2340         217 :     Guard<git_object> git_obj;
    2341         217 :     int result = 0;
    2342             : 
    2343         217 :     assert(commit != nullptr);
    2344         217 :     assert(ref != nullptr);
    2345         217 :     assert(m_repo != nullptr);
    2346             : 
    2347         217 :     result = git_reference_dwim(ref, m_repo, ref_str.c_str());
    2348         217 :     if (result == GIT_OK)
    2349             :     {
    2350         101 :         git_annotated_commit_from_ref(commit, m_repo, *ref);
    2351         101 :         return ESYSREPO_RESULT(ResultCode::OK);
    2352             :     }
    2353             : 
    2354         116 :     result = git_revparse_single(git_obj.get_p(), m_repo, ref_str.c_str());
    2355         116 :     if (result == GIT_OK)
    2356             :     {
    2357          41 :         result = git_annotated_commit_lookup(commit, m_repo, git_object_id(git_obj.get()));
    2358             :     }
    2359             : 
    2360         116 :     if (result == GIT_OK) return ESYSREPO_RESULT(ResultCode::OK);
    2361          75 :     return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
    2362         217 : }
    2363             : 
    2364           0 : Result GitImpl::find_ref(git_annotated_commit **commit, const std::string &ref, std::string &new_ref)
    2365             : {
    2366           0 :     std::vector<git::Remote> remotes;
    2367           0 :     int found_remotes = 0;
    2368             : 
    2369           0 :     Result rresult = get_remotes(remotes);
    2370           0 :     if (rresult.error()) return ESYSREPO_RESULT(rresult);
    2371             : 
    2372           0 :     int result = 0;
    2373             : 
    2374           0 :     for (auto &remote : remotes)
    2375             :     {
    2376           0 :         new_ref = "refs/remotes/" + remote.get_name() + "/" + ref;
    2377             : 
    2378           0 :         rresult = resolve_ref(commit, new_ref);
    2379           0 :         if (rresult.ok())
    2380             :         {
    2381           0 :             self()->debug(0, "found branch : " + new_ref);
    2382           0 :             ++found_remotes;
    2383             :         }
    2384             :     }
    2385           0 :     if (found_remotes == 1) return ESYSREPO_RESULT(ResultCode::OK);
    2386           0 :     return ESYSREPO_RESULT(ResultCode::GIT_FIND_REF_FAILED);
    2387           0 : }
    2388             : 
    2389          53 : Result GitImpl::find_ref(git_reference **ref, git_annotated_commit **commit, const std::string &ref_str,
    2390             :                          std::string &new_ref)
    2391             : {
    2392          53 :     std::vector<git::Remote> remotes;
    2393          53 :     int found_remotes = 0;
    2394             : 
    2395          53 :     Result rresult = get_remotes(remotes);
    2396          53 :     if (rresult.error()) return ESYSREPO_RESULT(rresult);
    2397             : 
    2398          53 :     int result = 0;
    2399         108 :     for (auto &remote : remotes)
    2400             :     {
    2401          55 :         new_ref = "refs/remotes/" + remote.get_name() + "/" + ref_str;
    2402             : 
    2403          55 :         rresult = resolve_ref(ref, commit, new_ref);
    2404          55 :         if (rresult.ok())
    2405             :         {
    2406          33 :             self()->debug(0, "found branch : " + new_ref);
    2407          33 :             ++found_remotes;
    2408             :         }
    2409             :     }
    2410          53 :     if (found_remotes == 1) return ESYSREPO_RESULT(ResultCode::OK);
    2411          20 :     return ESYSREPO_RESULT(ResultCode::GIT_FIND_REF_FAILED);
    2412          53 : }
    2413             : 
    2414         321 : Result GitImpl::convert_bin_hex(const git_oid &oid, std::string &hex_str)
    2415             : {
    2416         321 :     char temp[GIT_OID_HEXSZ + 1];
    2417             : 
    2418         321 :     int result = git_oid_fmt(temp, &oid);
    2419         321 :     if (result < 0) return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
    2420         321 :     temp[GIT_OID_HEXSZ] = 0;
    2421         321 :     hex_str = std::string(temp);
    2422         321 :     return ESYSREPO_RESULT(ResultCode::OK);
    2423             : }
    2424             : 
    2425          15 : Result GitImpl::convert_hex_bin(const std::string &hex_str, git_oid &oid)
    2426             : {
    2427          15 :     int result = git_oid_fromstrp(&oid, hex_str.c_str());
    2428          15 :     if (result == GIT_OK) return ESYSREPO_RESULT(ResultCode::OK);
    2429           0 :     return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
    2430             : }
    2431             : 
    2432           0 : const std::string &GitImpl::get_version()
    2433             : {
    2434           0 :     return s_get_version();
    2435             : }
    2436             : 
    2437           0 : const std::string &GitImpl::get_lib_name()
    2438             : {
    2439           0 :     return s_get_lib_name();
    2440             : }
    2441             : 
    2442         131 : void GitImpl::convert(git::BranchType branch_type, git_branch_t &list_flags)
    2443             : {
    2444         131 :     switch (branch_type)
    2445             :     {
    2446           0 :         case git::BranchType::ALL: list_flags = GIT_BRANCH_ALL; break;
    2447         131 :         case git::BranchType::LOCAL: list_flags = GIT_BRANCH_LOCAL; break;
    2448           0 :         case git::BranchType::REMOTE: list_flags = GIT_BRANCH_REMOTE; break;
    2449           0 :         default: list_flags = GIT_BRANCH_LOCAL;
    2450             :     }
    2451         131 : }
    2452             : 
    2453           0 : void GitImpl::set_agent_identity_path(const std::string &agent_identity_path)
    2454             : {
    2455           0 :     m_agent_identity_path = agent_identity_path;
    2456           0 : }
    2457             : 
    2458           0 : const std::string &GitImpl::get_agent_identity_path() const
    2459             : {
    2460           0 :     return m_agent_identity_path;
    2461             : }
    2462             : 
    2463         542 : void GitImpl::set_logger_if(std::shared_ptr<log::Logger_if> logger_if)
    2464             : {
    2465        1084 :     m_ssh.set_logger_if(logger_if);
    2466         542 : }
    2467             : 
    2468          17 : git_repository *GitImpl::get_repo()
    2469             : {
    2470          17 :     return m_repo;
    2471             : }
    2472             : 
    2473           7 : Result GitImpl::get_sigs(git_signature **author, git_signature **committer)
    2474             : {
    2475           7 :     std::shared_ptr<git::Person> person = self()->get_author();
    2476             : 
    2477           7 :     int result_int = git_signature_now(author, person->get_name().c_str(), person->get_email().c_str());
    2478           7 :     if (result_int < 0) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR);
    2479             : 
    2480           7 :     if (self()->get_committer() != nullptr) person = self()->get_committer();
    2481             : 
    2482           7 :     result_int = git_signature_now(committer, person->get_name().c_str(), person->get_email().c_str());
    2483           7 :     if (result_int < 0) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR);
    2484             : 
    2485           7 :     return ESYSREPO_RESULT(ResultCode::OK);
    2486           7 : }
    2487             : 
    2488          25 : std::string GitImpl::normalize_notes_ref(const std::string &reference)
    2489             : {
    2490          25 :     std::string the_ref = reference;
    2491             : 
    2492          25 :     if (reference.find("refs/notes/") == std::string::npos) the_ref = "refs/notes/" + reference;
    2493             : 
    2494          25 :     return the_ref;
    2495           0 : }
    2496             : 
    2497           2 : const std::string &GitImpl::s_get_version()
    2498             : {
    2499           2 :     static std::string s_version = LIBGIT2_VERSION;
    2500           2 :     return s_version;
    2501             : }
    2502             : 
    2503           2 : const std::string &GitImpl::s_get_lib_name()
    2504             : {
    2505           2 :     static std::string s_lib_name = "libgit2";
    2506           2 :     return s_lib_name;
    2507             : }
    2508             : 
    2509           2 : const std::string &GitImpl::s_get_ssh_version()
    2510             : {
    2511           2 :     static std::string s_version = LIBSSH2_VERSION;
    2512           2 :     return s_version;
    2513             : }
    2514             : 
    2515           2 : const std::string &GitImpl::s_get_ssh_lib_name()
    2516             : {
    2517           2 :     static std::string s_lib_name = "libssh2";
    2518           2 :     return s_lib_name;
    2519             : }
    2520             : 
    2521             : } // namespace esys::repo::libgit2

Generated by: LCOV version 1.14