LCOV - code coverage report
Current view: top level - src/esys/repo - gitbase.cpp (source / functions) Hit Total Coverage
Test: esysrepo_coverage.info Lines: 175 218 80.3 %
Date: 2026-11-07 22:18:26 Functions: 31 43 72.1 %

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

Generated by: LCOV version 1.14