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-01 10:43:40 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         211 : GitBase::GitBase()
      34         211 :     : log::User()
      35             : {
      36         211 : }
      37             : 
      38         214 : GitBase::~GitBase() = default;
      39             : 
      40          19 : void GitBase::set_author(std::shared_ptr<git::Person> person)
      41             : {
      42          19 :     m_person = person;
      43          19 : }
      44             : 
      45          29 : std::shared_ptr<git::Person> GitBase::get_author() const
      46             : {
      47          29 :     return m_person;
      48             : }
      49             : 
      50          16 : void GitBase::set_committer(std::shared_ptr<git::Person> person)
      51             : {
      52          16 :     m_committer = person;
      53          16 : }
      54             : 
      55          22 : std::shared_ptr<git::Person> GitBase::get_committer() const
      56             : {
      57          22 :     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         130 : bool GitBase::is_repo(const std::string &path)
      93             : {
      94         130 :     boost::filesystem::path git_path = path;
      95             : 
      96         130 :     git_path /= ".git";
      97             : 
      98         130 :     return boost::filesystem::exists(git_path);
      99         130 : }
     100             : 
     101          16 : void GitBase::set_id(std::size_t id)
     102             : {
     103          16 :     m_id = id;
     104          16 : }
     105             : 
     106          16 : std::size_t GitBase::get_id() const
     107             : {
     108          16 :     return m_id;
     109             : }
     110             : 
     111          16 : void GitBase::set_debug(bool debug)
     112             : {
     113          16 :     m_debug = debug;
     114          16 : }
     115             : 
     116          16 : bool GitBase::get_debug() const
     117             : {
     118          16 :     return m_debug;
     119             : }
     120             : 
     121        2291 : int GitBase::handle_sideband_progress(const std::string &text)
     122             : {
     123        2291 :     git::Progress progress;
     124        2291 :     int result = decode_sideband_progress(text, progress);
     125        2291 :     if (result != 0) return result;
     126             : 
     127             :     // Ignore non-progress chatter merged on stderr (e.g. "Cloning into...").
     128        2359 :     if (progress.get_fetch_step() == git::FetchStep::NOT_SET && progress.get_percentage() < 0
     129        2352 :         && !progress.get_done() && !progress.get_started())
     130             :         return 0;
     131             : 
     132        2246 :     notify_progress_throttled(progress);
     133             :     return 0;
     134             : }
     135             : 
     136          11 : int GitBase::handle_transfer_progress(const git::Progress &progress)
     137             : {
     138          11 :     notify_progress_throttled(progress);
     139          11 :     return 0;
     140             : }
     141             : 
     142      109115 : void GitBase::notify_progress_throttled(const git::Progress &progress)
     143             : {
     144             :     // Single-thread per GitBase for progress producers: throttle without locking.
     145      109115 :     constexpr auto k_min_emit_interval = std::chrono::milliseconds(50);
     146      109115 :     const auto now = std::chrono::steady_clock::now();
     147      109115 :     const bool step_changed = progress.get_fetch_step() != m_last_emitted_progress_step;
     148      109115 :     const bool due = !m_progress_emit_armed || (now - m_last_progress_emit) >= k_min_emit_interval;
     149      109115 :     if (!(progress.get_started() || progress.get_done() || step_changed || due)) return;
     150             : 
     151        1665 :     m_progress_emit_armed = true;
     152        1665 :     m_last_progress_emit = now;
     153        1665 :     m_last_emitted_progress_step = progress.get_fetch_step();
     154             : 
     155        1665 :     set_progress(progress);
     156        1665 :     if (m_progress_callback != nullptr) m_progress_callback->git_progress_notif(progress);
     157             : }
     158             : 
     159        1665 : void GitBase::set_progress(const git::Progress &progress)
     160             : {
     161        1665 :     std::lock_guard lock(m_progress_mutex);
     162             : 
     163        1665 :     m_progress = progress;
     164        1665 : }
     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         137 : void GitBase::set_progress_callback(ProgressCallbackBase *progress_callback)
     174             : {
     175         137 :     m_progress_callback = progress_callback;
     176         137 : }
     177             : 
     178          16 : ProgressCallbackBase *GitBase::get_progress_callback()
     179             : {
     180          16 :     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        2331 : int GitBase::decode_sideband_progress(const std::string &txt, git::Progress &progress)
     209             : {
     210        2331 :     std::string txt_input = txt;
     211             : 
     212        2331 :     std::replace(txt_input.begin(), txt_input.end(), '\r', '\n');
     213        2331 :     boost::to_lower(txt_input);
     214             : 
     215             :     // std::cout << "'" << txt_input << "'" << std::endl;
     216             : 
     217        2331 :     std::vector<std::string> lines;
     218        2331 :     boost::split(lines, txt_input, [](char c) { return c == '\n'; });
     219             : 
     220        2331 :     std::size_t percentage_idx = std::string::npos;
     221        2331 :     progress.set_percentage(-1);
     222        2331 :     bool percentage_error = false;
     223        2331 :     progress.set_fetch_step(git::FetchStep::NOT_SET);
     224             : 
     225       11354 :     for (auto &line : lines)
     226             :     {
     227        9023 :         boost::trim(line);
     228             : 
     229        9023 :         if (line.empty()) continue;
     230             : 
     231        8131 :         progress.set_done(false);
     232        8131 :         progress.set_percentage(-1);
     233             : 
     234        8131 :         if (line.find("done") != line.npos) progress.set_done(true);
     235        8131 :         percentage_idx = line.find("%");
     236             : 
     237        8131 :         if (percentage_idx != line.npos)
     238             :         {
     239        6423 :             std::string percentage_str = line.substr(0, percentage_idx);
     240        6423 :             std::size_t i = percentage_str.rfind(' ');
     241        6423 :             if (i != percentage_str.npos)
     242             :             {
     243        6238 :                 percentage_str = percentage_str.substr(i + 1);
     244        6238 :                 int result = 0;
     245        6238 :                 percentage_error = false;
     246        6238 :                 try
     247             :                 {
     248        6238 :                     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        6238 :                     progress.set_percentage(result);
     264             :             }
     265        6423 :         }
     266             : 
     267        8131 :         if (line.find("enumerating") != line.npos)
     268             :         {
     269         140 :             progress.set_fetch_step(git::FetchStep::ENUMERATING);
     270             :         }
     271        7991 :         else if (line.find("counting") != line.npos)
     272             :         {
     273        3015 :             progress.set_fetch_step(git::FetchStep::COUNTING);
     274             :         }
     275        4976 :         else if (line.find("compressing") != line.npos)
     276             :         {
     277        2899 :             progress.set_fetch_step(git::FetchStep::COMPRESSING);
     278             :         }
     279        2077 :         else if (line.find("receiving") != line.npos)
     280             :         {
     281           5 :             progress.set_fetch_step(git::FetchStep::RECEIVING);
     282             :         }
     283        2072 :         else if (line.find("resolving") != line.npos)
     284             :         {
     285           5 :             progress.set_fetch_step(git::FetchStep::RESOLVING);
     286             :         }
     287        2067 :         else if (line.find("total") != line.npos)
     288             :         {
     289         135 :             progress.set_fetch_step(git::FetchStep::TOTAL);
     290             :         }
     291             : 
     292             :         // Optional " (cur/tot)" counters, e.g. Receiving objects: 45% (123/456)
     293        8131 :         const auto lparen = line.find('(');
     294        8131 :         const auto slash = (lparen == line.npos) ? line.npos : line.find('/', lparen);
     295        6566 :         const auto rparen = (slash == line.npos) ? line.npos : line.find(')', slash);
     296        8131 :         if (lparen != line.npos && slash != line.npos && rparen != line.npos && rparen > slash + 1)
     297             :         {
     298        6145 :             try
     299             :             {
     300       12290 :                 const int cur = std::stoi(line.substr(lparen + 1, slash - lparen - 1));
     301       12290 :                 const int tot = std::stoi(line.substr(slash + 1, rparen - slash - 1));
     302        6145 :                 const auto step = progress.get_fetch_step();
     303        6145 :                 if (step == git::FetchStep::RESOLVING)
     304             :                 {
     305           5 :                     progress.set_indexed_deltas(cur);
     306           5 :                     progress.set_total_deltas(tot);
     307             :                 }
     308        6140 :                 else if (step == git::FetchStep::RECEIVING || step == git::FetchStep::COUNTING
     309        3513 :                          || step == git::FetchStep::COMPRESSING || step == git::FetchStep::ENUMERATING)
     310             :                 {
     311        5027 :                     progress.set_received_objects(cur);
     312        5027 :                     progress.set_total_objects(tot);
     313        5027 :                     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        4662 :     return 0;
     323        2331 : }
     324             : 
     325         598 : void GitBase::cmd_start()
     326             : {
     327         598 :     ETRC_CALL_NP();
     328             : 
     329         598 :     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         598 :     auto ms = std::chrono::time_point_cast<std::chrono::milliseconds>(m_last_cmd_start_time).time_since_epoch().count();
     333         598 :     ETRC_MSG_OS("last_cmd_start_time : " << ms);
     334             : 
     335         598 :     git::Progress progress;
     336         598 :     progress.set_started(true);
     337         598 :     notify_progress_throttled(progress);
     338         598 : }
     339             : 
     340         711 : void GitBase::cmd_end()
     341             : {
     342         711 :     ETRC_CALL_NP();
     343             : 
     344         711 :     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         711 :     auto ms = std::chrono::time_point_cast<std::chrono::milliseconds>(m_last_cmd_end_time).time_since_epoch().count();
     348         711 :     ETRC_MSG_OS("last_cmd_end_time : " << ms);
     349         711 : }
     350             : 
     351         303 : void GitBase::open_time()
     352             : {
     353         303 :     m_open_time = std::chrono::steady_clock::now();
     354         303 : }
     355             : 
     356         306 : void GitBase::close_time()
     357             : {
     358         306 :     m_close_time = std::chrono::steady_clock::now();
     359         306 : }
     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         201 : uint64_t GitBase::take_last_refresh_ms()
     381             : {
     382         201 :     return 0;
     383             : }
     384             : 
     385         205 : GitBase &GitBase::ssh_probe()
     386             : {
     387         205 :     static std::shared_ptr<GitBase> instance = GitMngr::new_ptr();
     388         205 :     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         195 : Result_t<git::SshBackend> GitBase::get_ssh_backend()
     407             : {
     408         195 :     return ssh_probe().do_get_ssh_backend();
     409             : }
     410             : 
     411             : } // namespace esys::repo

Generated by: LCOV version 1.14