LCOV - code coverage report
Current view: top level - src/esys/repo - gitbase.cpp (source / functions) Hit Total Coverage
Test: esysrepo_coverage.info Lines: 175 206 85.0 %
Date: 2026-08-12 14:26:50 Functions: 31 37 83.8 %

          Line data    Source code
       1             : /*!
       2             :  * \file esys/repo/gitbase.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/gitbase.h"
      20             : #include "esys/repo/gitmngr.h"
      21             : 
      22             : #include <esys/trace/call.h>
      23             : #include <esys/trace/macros.h>
      24             : 
      25             : #include <boost/filesystem.hpp>
      26             : #include <boost/algorithm/string.hpp>
      27             : 
      28             : #include <iostream>
      29             : 
      30             : namespace esys::repo
      31             : {
      32             : 
      33         506 : GitBase::GitBase()
      34         506 :     : log::User()
      35             : {
      36         506 : }
      37             : 
      38         511 : GitBase::~GitBase() = default;
      39             : 
      40         911 : void GitBase::set_author(std::shared_ptr<git::Person> person)
      41             : {
      42         911 :     m_person = person;
      43         911 : }
      44             : 
      45         921 : std::shared_ptr<git::Person> GitBase::get_author() const
      46             : {
      47         921 :     return m_person;
      48             : }
      49             : 
      50         908 : void GitBase::set_committer(std::shared_ptr<git::Person> person)
      51             : {
      52         908 :     m_committer = person;
      53         908 : }
      54             : 
      55         914 : std::shared_ptr<git::Person> GitBase::get_committer() const
      56             : {
      57         914 :     return m_committer;
      58             : }
      59             : 
      60           2 : Result GitBase::reset_to_parent(int nth_parent)
      61             : {
      62           2 :     int result = is_open();
      63             : 
      64           2 :     if (result < 0) return ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN);
      65             : 
      66           2 :     git::CommitHash last_commit;
      67           2 :     git::CommitHash parent_commit;
      68             : 
      69             :     // Get the last commit in the manifest git repo
      70           2 :     Result rresult = get_last_commit(last_commit);
      71           2 :     if (rresult.error()) return ESYSREPO_RESULT(rresult);
      72             : 
      73             :     // Get the its parent commit
      74           2 :     rresult = get_parent_commit(last_commit, parent_commit);
      75           2 :     if (rresult.error()) return ESYSREPO_RESULT(rresult);
      76             : 
      77             :     // Reset the manifest git repo to the parent commit
      78           2 :     rresult = reset(parent_commit, git::ResetType::HARD);
      79           2 :     if (rresult.error()) return ESYSREPO_RESULT(rresult);
      80             : 
      81           2 :     git::CommitHash new_last_commit;
      82             : 
      83             :     // Get the new last commit
      84           2 :     rresult = get_last_commit(new_last_commit);
      85           2 :     if (rresult.error()) return ESYSREPO_RESULT(rresult);
      86             : 
      87             :     // The new last commit should be the parent commit
      88           2 :     if (parent_commit.get_hash() != new_last_commit.get_hash()) return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
      89           2 :     return ESYSREPO_RESULT(ResultCode::OK);
      90           2 : }
      91             : 
      92         343 : bool GitBase::is_repo(const std::string &path)
      93             : {
      94         343 :     boost::filesystem::path git_path = path;
      95             : 
      96         343 :     git_path /= ".git";
      97             : 
      98         343 :     return boost::filesystem::exists(git_path);
      99         343 : }
     100             : 
     101         908 : void GitBase::set_id(std::size_t id)
     102             : {
     103         908 :     m_id = id;
     104         908 : }
     105             : 
     106         908 : std::size_t GitBase::get_id() const
     107             : {
     108         908 :     return m_id;
     109             : }
     110             : 
     111         908 : void GitBase::set_debug(bool debug)
     112             : {
     113         908 :     m_debug = debug;
     114         908 : }
     115             : 
     116         908 : bool GitBase::get_debug() const
     117             : {
     118         908 :     return m_debug;
     119             : }
     120             : 
     121       17662 : int GitBase::handle_sideband_progress(const std::string &text)
     122             : {
     123       17662 :     git::Progress progress;
     124       17662 :     int result = decode_sideband_progress(text, progress);
     125       17662 :     if (result != 0) return result;
     126             : 
     127             :     // Ignore non-progress chatter merged on stderr (e.g. "Cloning into...").
     128       17865 :     if (progress.get_fetch_step() == git::FetchStep::NOT_SET && progress.get_percentage() < 0
     129       17865 :         && !progress.get_done() && !progress.get_started())
     130             :         return 0;
     131             : 
     132       17470 :     notify_progress_throttled(progress);
     133             :     return 0;
     134             : }
     135             : 
     136         183 : int GitBase::handle_transfer_progress(const git::Progress &progress)
     137             : {
     138         183 :     notify_progress_throttled(progress);
     139         183 :     return 0;
     140             : }
     141             : 
     142       18737 : void GitBase::notify_progress_throttled(const git::Progress &progress)
     143             : {
     144             :     // Single-thread per GitBase for progress producers: throttle without locking.
     145       18737 :     constexpr auto k_min_emit_interval = std::chrono::milliseconds(50);
     146       18737 :     const auto now = std::chrono::steady_clock::now();
     147       18737 :     const bool step_changed = progress.get_fetch_step() != m_last_emitted_progress_step;
     148       18737 :     const bool due = !m_progress_emit_armed || (now - m_last_progress_emit) >= k_min_emit_interval;
     149       18737 :     if (!(progress.get_started() || progress.get_done() || step_changed || due)) return;
     150             : 
     151        2061 :     m_progress_emit_armed = true;
     152        2061 :     m_last_progress_emit = now;
     153        2061 :     m_last_emitted_progress_step = progress.get_fetch_step();
     154             : 
     155        2061 :     set_progress(progress);
     156        2061 :     if (m_progress_callback != nullptr) m_progress_callback->git_progress_notif(progress);
     157             : }
     158             : 
     159        2061 : void GitBase::set_progress(const git::Progress &progress)
     160             : {
     161        2061 :     std::lock_guard lock(m_progress_mutex);
     162             : 
     163        2061 :     m_progress = progress;
     164        2061 : }
     165             : 
     166           0 : void GitBase::get_progress(git::Progress &progress)
     167             : {
     168           0 :     std::lock_guard lock(m_progress_mutex);
     169             : 
     170           0 :     progress = m_progress;
     171           0 : }
     172             : 
     173        1029 : void GitBase::set_progress_callback(ProgressCallbackBase *progress_callback)
     174             : {
     175        1029 :     m_progress_callback = progress_callback;
     176        1029 : }
     177             : 
     178         908 : ProgressCallbackBase *GitBase::get_progress_callback()
     179             : {
     180         908 :     return m_progress_callback;
     181             : }
     182             : 
     183           0 : void GitBase::clear_notes_ref()
     184             : {
     185           0 :     m_notes_references.clear();
     186           0 :     m_map_notes_references.clear();
     187           0 : }
     188             : 
     189           5 : int GitBase::add_notes_ref(const std::string &notes_ref)
     190             : {
     191           5 :     if (m_map_notes_references.find(notes_ref) != m_map_notes_references.end()) return -1;
     192             : 
     193           5 :     m_map_notes_references[notes_ref] = true;
     194           5 :     m_notes_references.push_back(notes_ref);
     195           5 :     return 0;
     196             : }
     197             : 
     198           6 : std::vector<std::string> &GitBase::get_notes_references()
     199             : {
     200           6 :     return m_notes_references;
     201             : }
     202             : 
     203           0 : const std::vector<std::string> &GitBase::get_notes_references() const
     204             : {
     205           0 :     return m_notes_references;
     206             : }
     207             : 
     208       17702 : int GitBase::decode_sideband_progress(const std::string &txt, git::Progress &progress)
     209             : {
     210       17702 :     std::string txt_input = txt;
     211             : 
     212       17702 :     std::replace(txt_input.begin(), txt_input.end(), '\r', '\n');
     213       17702 :     boost::to_lower(txt_input);
     214             : 
     215             :     // std::cout << "'" << txt_input << "'" << std::endl;
     216             : 
     217       17702 :     std::vector<std::string> lines;
     218       17702 :     boost::split(lines, txt_input, [](char c) { return c == '\n'; });
     219             : 
     220       17702 :     std::size_t percentage_idx = std::string::npos;
     221       17702 :     progress.set_percentage(-1);
     222       17702 :     bool percentage_error = false;
     223       17702 :     progress.set_fetch_step(git::FetchStep::NOT_SET);
     224             : 
     225       35903 :     for (auto &line : lines)
     226             :     {
     227       18201 :         boost::trim(line);
     228             : 
     229       18201 :         if (line.empty()) continue;
     230             : 
     231       18091 :         progress.set_done(false);
     232       18091 :         progress.set_percentage(-1);
     233             : 
     234       18091 :         if (line.find("done") != line.npos) progress.set_done(true);
     235       18091 :         percentage_idx = line.find("%");
     236             : 
     237       18091 :         if (percentage_idx != line.npos)
     238             :         {
     239       17515 :             std::string percentage_str = line.substr(0, percentage_idx);
     240       17515 :             std::size_t i = percentage_str.rfind(' ');
     241       17515 :             if (i != percentage_str.npos)
     242             :             {
     243       17497 :                 percentage_str = percentage_str.substr(i + 1);
     244       17497 :                 int result = 0;
     245       17497 :                 percentage_error = false;
     246       17497 :                 try
     247             :                 {
     248       17497 :                     result = std::stoi(percentage_str);
     249             :                 }
     250           0 :                 catch (std::invalid_argument &)
     251             :                 {
     252           0 :                     percentage_error = true;
     253           0 :                     progress.set_percentage(-1);
     254           0 :                 }
     255           0 :                 catch (std::out_of_range &)
     256             :                 {
     257           0 :                     percentage_error = true;
     258           0 :                     progress.set_percentage(-1);
     259           0 :                 }
     260           0 :                 if (percentage_error)
     261           0 :                     progress.set_percentage(-1);
     262             :                 else
     263       17497 :                     progress.set_percentage(result);
     264             :             }
     265       17515 :         }
     266             : 
     267       18091 :         if (line.find("enumerating") != line.npos)
     268             :         {
     269         151 :             progress.set_fetch_step(git::FetchStep::ENUMERATING);
     270             :         }
     271       17940 :         else if (line.find("counting") != line.npos)
     272             :         {
     273        4235 :             progress.set_fetch_step(git::FetchStep::COUNTING);
     274             :         }
     275       13705 :         else if (line.find("compressing") != line.npos)
     276             :         {
     277        3469 :             progress.set_fetch_step(git::FetchStep::COMPRESSING);
     278             :         }
     279       10236 :         else if (line.find("receiving") != line.npos)
     280             :         {
     281        5772 :             progress.set_fetch_step(git::FetchStep::RECEIVING);
     282             :         }
     283        4464 :         else if (line.find("resolving") != line.npos)
     284             :         {
     285        4020 :             progress.set_fetch_step(git::FetchStep::RESOLVING);
     286             :         }
     287         444 :         else if (line.find("total") != line.npos)
     288             :         {
     289         146 :             progress.set_fetch_step(git::FetchStep::TOTAL);
     290             :         }
     291             : 
     292             :         // Optional " (cur/tot)" counters, e.g. Receiving objects: 45% (123/456)
     293       18091 :         const auto lparen = line.find('(');
     294       18091 :         const auto slash = (lparen == line.npos) ? line.npos : line.find('/', lparen);
     295       17661 :         const auto rparen = (slash == line.npos) ? line.npos : line.find(')', slash);
     296       18091 :         if (lparen != line.npos && slash != line.npos && rparen != line.npos && rparen > slash + 1)
     297             :         {
     298       17506 :             try
     299             :             {
     300       35012 :                 const int cur = std::stoi(line.substr(lparen + 1, slash - lparen - 1));
     301       35012 :                 const int tot = std::stoi(line.substr(slash + 1, rparen - slash - 1));
     302       17506 :                 const auto step = progress.get_fetch_step();
     303       17506 :                 if (step == git::FetchStep::RESOLVING)
     304             :                 {
     305        4020 :                     progress.set_indexed_deltas(cur);
     306        4020 :                     progress.set_total_deltas(tot);
     307             :                 }
     308       13486 :                 else if (step == git::FetchStep::RECEIVING || step == git::FetchStep::COUNTING
     309        3509 :                          || step == git::FetchStep::COMPRESSING || step == git::FetchStep::ENUMERATING)
     310             :                 {
     311       13419 :                     progress.set_received_objects(cur);
     312       13419 :                     progress.set_total_objects(tot);
     313       13419 :                     if (step == git::FetchStep::RECEIVING) progress.set_indexed_objects(cur);
     314             :                 }
     315             :             }
     316           0 :             catch (const std::exception &)
     317             :             {
     318             :                 // Ignore malformed counters; percentage/step still useful.
     319           0 :             }
     320             :         }
     321             :     }
     322       35404 :     return 0;
     323       17702 : }
     324             : 
     325         547 : void GitBase::cmd_start()
     326             : {
     327         547 :     ETRC_CALL_NP();
     328             : 
     329         547 :     m_last_cmd_start_time = std::chrono::steady_clock::now();
     330             : 
     331             :     // Cast the time point to ms, then get its duration, then get the duration's count.
     332         547 :     auto ms = std::chrono::time_point_cast<std::chrono::milliseconds>(m_last_cmd_start_time).time_since_epoch().count();
     333         547 :     ETRC_MSG_OS("last_cmd_start_time : " << ms);
     334             : 
     335         547 :     git::Progress progress;
     336         547 :     progress.set_started(true);
     337         547 :     notify_progress_throttled(progress);
     338         547 : }
     339             : 
     340         595 : void GitBase::cmd_end()
     341             : {
     342         595 :     ETRC_CALL_NP();
     343             : 
     344         595 :     m_last_cmd_end_time = std::chrono::steady_clock::now();
     345             : 
     346             :     // Cast the time point to ms, then get its duration, then get the duration's count.
     347         595 :     auto ms = std::chrono::time_point_cast<std::chrono::milliseconds>(m_last_cmd_end_time).time_since_epoch().count();
     348         595 :     ETRC_MSG_OS("last_cmd_end_time : " << ms);
     349         595 : }
     350             : 
     351         762 : void GitBase::open_time()
     352             : {
     353         762 :     m_open_time = std::chrono::steady_clock::now();
     354         762 : }
     355             : 
     356         765 : void GitBase::close_time()
     357             : {
     358         765 :     m_close_time = std::chrono::steady_clock::now();
     359         765 : }
     360             : 
     361           0 : uint64_t GitBase::get_open_time()
     362             : {
     363           0 :     if (m_close_time < m_open_time) return 0;
     364             : 
     365           0 :     auto d_milli = std::chrono::duration_cast<std::chrono::milliseconds>(m_close_time - m_open_time).count();
     366             : 
     367           0 :     return static_cast<uint64_t>(d_milli);
     368             : }
     369             : 
     370           0 : uint64_t GitBase::get_last_cmd_elapsed_time()
     371             : {
     372           0 :     if (m_last_cmd_end_time < m_last_cmd_start_time) return 0;
     373             : 
     374           0 :     auto d_milli =
     375           0 :         std::chrono::duration_cast<std::chrono::milliseconds>(m_last_cmd_end_time - m_last_cmd_start_time).count();
     376             : 
     377           0 :     return static_cast<uint64_t>(d_milli);
     378             : }
     379             : 
     380          42 : uint64_t GitBase::take_last_refresh_ms()
     381             : {
     382          42 :     return 0;
     383             : }
     384             : 
     385          61 : GitBase &GitBase::ssh_probe()
     386             : {
     387          61 :     static std::shared_ptr<GitBase> instance = GitMngr::new_ptr();
     388          61 :     return *instance;
     389             : }
     390             : 
     391           3 : bool GitBase::is_ssh_backend_supported(git::SshBackend backend)
     392             : {
     393           3 :     return ssh_probe().do_is_ssh_backend_supported(backend);
     394             : }
     395             : 
     396           2 : bool GitBase::is_ssh_backend_available(git::SshBackend backend, bool force)
     397             : {
     398           2 :     return ssh_probe().do_is_ssh_backend_available(backend, force);
     399             : }
     400             : 
     401           5 : Result GitBase::set_ssh_backend(git::SshBackend backend)
     402             : {
     403           5 :     return ssh_probe().do_set_ssh_backend(backend);
     404             : }
     405             : 
     406          51 : Result_t<git::SshBackend> GitBase::get_ssh_backend()
     407             : {
     408          51 :     return ssh_probe().do_get_ssh_backend();
     409             : }
     410             : 
     411             : } // namespace esys::repo

Generated by: LCOV version 1.14