LCOV - code coverage report
Current view: top level - src/esys/repo/exe - cmdsync.cpp (source / functions) Hit Total Coverage
Test: esysrepo_coverage.info Lines: 115 291 39.5 %
Date: 2026-08-12 14:26:50 Functions: 19 31 61.3 %

          Line data    Source code
       1             : /*!
       2             :  * \file esys/repo/exe/cmdsync.cpp
       3             :  * \brief
       4             :  *
       5             :  * \cond
       6             :  * __legal_b__
       7             :  *
       8             :  * Copyright (c) 2020-2022 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/exe/cmdsync.h"
      20             : #include "esys/repo/manifest/capture.h"
      21             : #include "esys/repo/manifest/sync.h"
      22             : #include "esys/repo/manifest/syncrepos.h"
      23             : #include "esys/repo/resultcode.h"
      24             : #include "esys/repo/tui/is_tty.h"
      25             : #include "esys/repo/tui/run_compact_sync_tui.h"
      26             : 
      27             : #include <esys/trace/call.h>
      28             : #include <esys/trace/macros.h>
      29             : 
      30             : #include <boost/filesystem.hpp>
      31             : 
      32             : #include <cstdlib>
      33             : #include <cstring>
      34             : #include <iostream>
      35             : #include <sstream>
      36             : 
      37             : namespace esys::repo::exe
      38             : {
      39             : 
      40             : namespace
      41             : {
      42             : 
      43          20 : bool env_flag_enabled(const char *name)
      44             : {
      45          20 :     const char *value = std::getenv(name);
      46          20 :     if (value == nullptr || value[0] == '\0') return false;
      47           0 :     if (std::strcmp(value, "1") == 0) return true;
      48           0 :     if (std::strcmp(value, "true") == 0) return true;
      49           0 :     if (std::strcmp(value, "TRUE") == 0) return true;
      50           0 :     if (std::strcmp(value, "yes") == 0) return true;
      51           0 :     if (std::strcmp(value, "YES") == 0) return true;
      52           0 :     if (std::strcmp(value, "on") == 0) return true;
      53           0 :     if (std::strcmp(value, "ON") == 0) return true;
      54             :     return false;
      55             : }
      56             : 
      57             : } // namespace
      58             : 
      59          31 : CmdSync::CmdSync()
      60          62 :     : Cmd("Sync")
      61             : {
      62          31 : }
      63             : 
      64          31 : CmdSync::~CmdSync() = default;
      65             : 
      66           1 : void CmdSync::set_force(bool force)
      67             : {
      68           1 :     m_force = force;
      69           1 : }
      70             : 
      71          21 : bool CmdSync::get_force() const
      72             : {
      73          21 :     return m_force;
      74             : }
      75             : 
      76           2 : void CmdSync::set_branch(const std::string &branch)
      77             : {
      78           2 :     m_branch = branch;
      79           2 : }
      80             : 
      81          44 : const std::string &CmdSync::get_branch() const
      82             : {
      83          44 :     return m_branch;
      84             : }
      85             : 
      86           2 : void CmdSync::set_rev_set(const std::string &rev_set)
      87             : {
      88           2 :     m_rev_set = rev_set;
      89           2 : }
      90             : 
      91          42 : const std::string &CmdSync::get_rev_set() const
      92             : {
      93          42 :     return m_rev_set;
      94             : }
      95             : 
      96          20 : Result CmdSync::apply_revision_set_selection()
      97             : {
      98          20 :     Result result;
      99          20 :     ETRC_CALL_RET_NP(result);
     100             : 
     101          40 :     if (get_manifest() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::MANIFEST_IS_NULLPTR));
     102             : 
     103          20 :     std::string preferred = get_rev_set();
     104          80 :     if (preferred.empty() && get_config_folder() != nullptr && get_config_folder()->get_config() != nullptr)
     105          80 :         preferred = get_config_folder()->get_config()->get_revision_set();
     106             : 
     107          20 :     result = get_manifest()->select_revision_set(preferred);
     108          20 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     109             : 
     110          20 :     if (!get_rev_set().empty() && get_config_folder() != nullptr)
     111             :     {
     112           0 :         auto cfg = get_config_folder()->get_or_new_config();
     113           0 :         cfg->set_revision_set(get_rev_set());
     114           0 :         result = get_config_folder()->write_config_file();
     115           0 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     116           0 :     }
     117             : 
     118          40 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     119          20 : }
     120             : 
     121          20 : Result CmdSync::apply_revision_pins()
     122             : {
     123          20 :     Result result;
     124          20 :     ETRC_CALL_RET_NP(result);
     125             : 
     126          40 :     if (get_manifest() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::MANIFEST_IS_NULLPTR));
     127          40 :     if (get_config_folder() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::CONFIG_FOLDER_IS_NULLPTR));
     128             : 
     129             :     // open() already loaded pins; refresh in case they changed mid-session
     130          20 :     result = get_config_folder()->load_pins_file();
     131          20 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     132             : 
     133          60 :     get_manifest()->set_revision_pins(get_config_folder()->get_or_new_revision_pins());
     134          40 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     135          20 : }
     136             : 
     137           1 : void CmdSync::set_alt_address(bool alt_address)
     138             : {
     139           1 :     m_alt_address = alt_address;
     140           1 : }
     141           1 : bool CmdSync::get_alt_address() const
     142             : {
     143           1 :     return m_alt_address;
     144             : }
     145             : 
     146           0 : void CmdSync::set_multi(bool multi)
     147             : {
     148           0 :     m_multi = multi;
     149           0 : }
     150             : 
     151          20 : bool CmdSync::get_multi() const
     152             : {
     153          20 :     return m_multi;
     154             : }
     155             : 
     156           0 : void CmdSync::set_tui(bool tui)
     157             : {
     158           0 :     m_tui = tui;
     159           0 : }
     160             : 
     161          20 : bool CmdSync::get_tui() const
     162             : {
     163          20 :     return m_tui;
     164             : }
     165             : 
     166           0 : void CmdSync::set_tui_demo(bool tui_demo)
     167             : {
     168           0 :     m_tui_demo = tui_demo;
     169           0 :     if (tui_demo) m_tui = true;
     170           0 : }
     171             : 
     172           0 : bool CmdSync::get_tui_demo() const
     173             : {
     174           0 :     return m_tui_demo;
     175             : }
     176             : 
     177           3 : void CmdSync::set_recurse_submodules(manifest::RecurseSubmodules recurse)
     178             : {
     179           3 :     m_recurse_submodules = recurse;
     180           3 : }
     181             : 
     182          23 : manifest::RecurseSubmodules CmdSync::get_recurse_submodules() const
     183             : {
     184          23 :     return m_recurse_submodules;
     185             : }
     186             : 
     187          20 : Result CmdSync::sync_manifest()
     188             : {
     189          20 :     Result result;
     190          20 :     ETRC_CALL_RET_NP(result);
     191             : 
     192          20 :     manifest::Sync sync;
     193             : 
     194          40 :     if (get_config_folder() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::CMD_CONFIG_FOLDER_NULLPTR));
     195             : 
     196          20 :     sync.set_config_folder(get_config_folder());
     197          20 :     sync.set_git(get_git());
     198          20 :     sync.set_logger_if(get_logger_if());
     199          20 :     if (!get_branch().empty()) sync.set_branch(get_branch());
     200             : 
     201          40 :     return ETRC_RET(ESYSREPO_RESULT(sync.run()));
     202          20 : }
     203             : 
     204           0 : Result CmdSync::handle_capture_item(std::shared_ptr<manifest::Capture::Item> item)
     205             : {
     206           0 :     Result result;
     207           0 :     ETRC_CALL_RET(result, item);
     208             : 
     209           0 :     if (get_workspace_path().empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::CMD_WORKSPACE_PATH_IS_EMPTY));
     210             : 
     211           0 :     boost::filesystem::path git_path_folder = get_workspace_path();
     212           0 :     git_path_folder /= item->get_path();
     213           0 :     git_path_folder = git_path_folder.lexically_normal().make_preferred();
     214             : 
     215           0 :     auto git_helper = new_git_helper();
     216           0 :     result = git_helper->open(git_path_folder.string(), log::Level::DEBUG);
     217           0 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     218             : 
     219           0 :     GitHelper::AutoClose auto_close(git_helper.get());
     220             : 
     221           0 :     bool dirty = false;
     222           0 :     result = git_helper->is_dirty(dirty, log::Level::DEBUG);
     223           0 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     224             : 
     225             :     // If there is a remote defined, create it and fetch it
     226           0 :     if (!item->get_remote_name().empty())
     227             :     {
     228           0 :         result = git_helper->get_git()->add_remote(item->get_remote_name(), item->get_remote_url());
     229           0 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     230             : 
     231           0 :         result = git_helper->get_git()->fetch(item->get_remote_name());
     232           0 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     233             :     }
     234             : 
     235             :     // Checkout the new revision
     236           0 :     result = git_helper->checkout(item->get_revision(), false, log::Level::DEBUG);
     237           0 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     238             : 
     239           0 :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     240           0 : }
     241             : 
     242           0 : Result CmdSync::do_multi()
     243             : {
     244           0 :     Result result;
     245           0 :     ETRC_CALL_RET_NP(result);
     246             : 
     247             :     // First we need to get the working folder
     248           0 :     std::string folder_path;
     249             : 
     250           0 :     if (!get_folder().empty())
     251           0 :         folder_path = get_folder();
     252           0 :     else if (!get_workspace_path().empty())
     253           0 :         folder_path = get_workspace_path();
     254             :     else
     255           0 :         folder_path = boost::filesystem::current_path().string();
     256           0 :     if (folder_path.empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
     257             : 
     258           0 :     auto git_folder_path = find_git_root_path(folder_path);
     259           0 :     auto git_helper = new_git_helper();
     260             : 
     261           0 :     set_git_folder_path(git_folder_path);
     262           0 :     result = git_helper->open(git_folder_path, log::Level::DEBUG);
     263           0 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     264             : 
     265           0 :     GitHelper::AutoClose auto_close(git_helper.get());
     266             : 
     267           0 :     int result_int = git_helper->get_git()->add_notes_ref("esysrepo");
     268           0 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
     269             : 
     270           0 :     result = git_helper->get_git()->fetch_all_notes();
     271           0 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     272             : 
     273           0 :     git::Commit last_commit;
     274             : 
     275           0 :     result = git_helper->get_git()->get_last_commit(last_commit, true);
     276           0 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     277             : 
     278           0 :     std::vector<std::shared_ptr<git::Note>> notes;
     279           0 :     result_int = last_commit.get_notes("esysrepo", notes);
     280           0 :     if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
     281             : 
     282           0 :     std::ostringstream oss;
     283             : 
     284           0 :     oss << "Found " << notes.size() << " esysrepo multi note.";
     285             : 
     286           0 :     info(oss.str());
     287             : 
     288           0 :     if (notes.empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     289           0 :     if (notes.size() != 1) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
     290             : 
     291           0 :     std::string note_text = notes[0]->get_message();
     292           0 :     auto capture = std::make_shared<manifest::Capture>();
     293           0 :     result = load_capture(capture, note_text);
     294           0 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     295             : 
     296           0 :     oss.str("");
     297           0 :     print_capture(oss, capture);
     298           0 :     info(oss.str());
     299             : 
     300           0 :     Result rresult;
     301           0 :     for (auto repo : capture->get_items())
     302             :     {
     303           0 :         result = handle_capture_item(repo);
     304           0 :         if (result.error())
     305             :         {
     306           0 :             std::ostringstream e_oss;
     307             : 
     308           0 :             e_oss << "repo '" << repo->get_path() << "' could set revivions '" << repo->get_revision() << "'";
     309           0 :             error(e_oss.str());
     310           0 :             rresult.add(result);
     311           0 :         }
     312           0 :     }
     313             : 
     314           0 :     return ETRC_RET(ESYSREPO_RESULT(rresult));
     315           0 : }
     316             : 
     317           0 : void CmdSync::set_git_folder_path(const std::string &git_folder_path)
     318             : {
     319           0 :     m_git_folder_path = git_folder_path;
     320           0 : }
     321             : 
     322           0 : const std::string &CmdSync::get_git_folder_path() const
     323             : {
     324           0 :     return m_git_folder_path;
     325             : }
     326             : 
     327          20 : Result CmdSync::impl_run()
     328             : {
     329          20 :     Result result;
     330          20 :     ETRC_CALL_RET_NP(result);
     331             : 
     332          20 :     if (env_flag_enabled("ESYSREPO_TUI_DEMO")) set_tui_demo(true);
     333             : 
     334          20 :     if (get_tui()) return ETRC_RET(ESYSREPO_RESULT(impl_run_tui()));
     335             : 
     336          20 :     if (get_force()) warn("Option --force-sync is not implemented yet");
     337             : 
     338          20 :     result = default_handling_folder_workspace();
     339          20 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     340             : 
     341          20 :     result = open_esysrepo_folder();
     342          20 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     343             : 
     344          20 :     result = sync_manifest();
     345          20 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     346             : 
     347          20 :     result = load_manifest();
     348          20 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     349             : 
     350          20 :     result = apply_revision_set_selection();
     351          20 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     352             : 
     353          20 :     result = apply_revision_pins();
     354          20 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     355             : 
     356          20 :     manifest::SyncRepos sync_repos;
     357             : 
     358          20 :     if (!get_branch().empty()) sync_repos.set_branch(get_branch());
     359             : 
     360          20 :     if (get_sub_args().size() != 0)
     361           4 :         sync_repos.set_folders_to_sync(get_sub_args());
     362          16 :     else if (get_groups().size() != 0)
     363             :     {
     364           2 :         std::vector<std::string> repos;
     365             : 
     366           4 :         for (auto &group : get_groups())
     367             :         {
     368           2 :             auto group_ptr = get_manifest()->get_groups().find_group_by_name(group);
     369           2 :             if (group_ptr == nullptr)
     370             :             {
     371             :                 //! \TODO
     372           0 :                 continue;
     373             :             }
     374           8 :             for (auto repo : group_ptr->get_repos())
     375             :             {
     376           6 :                 repos.push_back(repo->get_path());
     377           6 :             }
     378           2 :         }
     379             : 
     380           2 :         sync_repos.set_folders_to_sync(repos);
     381           2 :     }
     382             : 
     383          20 :     if (get_debug()) sync_repos.set_log_level(log::Level::DEBUG);
     384             : 
     385          20 :     sync_repos.set_job_count(get_job_count());
     386          20 :     sync_repos.set_logger_if(get_logger_if());
     387          20 :     sync_repos.set_config_folder(get_config_folder());
     388          20 :     sync_repos.set_git(get_git());
     389          20 :     sync_repos.set_manifest(get_manifest());
     390          20 :     sync_repos.set_tui(false);
     391          20 :     sync_repos.set_recurse_submodules(get_recurse_submodules());
     392             : 
     393          20 :     get_git()->detect_ssh_agent(true);
     394             : 
     395          20 :     result = sync_repos.run();
     396          20 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     397             : 
     398          40 :     if (!get_multi()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     399           0 :     return ETRC_RET(ESYSREPO_RESULT(do_multi()));
     400          20 : }
     401             : 
     402           0 : Result CmdSync::impl_run_tui()
     403             : {
     404           0 :     Result result;
     405           0 :     ETRC_CALL_RET_NP(result);
     406             : 
     407             : #ifndef ESYSREPO_HAVE_TUI
     408             :     error("--tui requested but esysrepo was built without FTXUI (Conan with_tui / ESYSREPO_WITH_TUI)");
     409             :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::NOT_IMPLEMENTED));
     410             : #else
     411           0 :     if (!tui::is_tty())
     412             :     {
     413           0 :         const char *msg =
     414             :             "--tui requires a real Win32 console (cmd.exe, PowerShell, or Windows "
     415             :             "Terminal). Git Bash/mintty and redirected stdout are not supported.";
     416           0 :         error(msg);
     417           0 :         std::cerr << "ERROR: " << msg << std::endl;
     418           0 :         return ETRC_RET(ESYSREPO_RESULT(ResultCode::GENERIC_ERROR));
     419             :     }
     420             : 
     421           0 :     if (get_force()) warn("Option --force-sync is not implemented yet");
     422             : 
     423           0 :     const std::size_t ui_workers =
     424           0 :         get_job_count() <= 0 ? std::size_t{1} : static_cast<std::size_t>(get_job_count());
     425             : 
     426           0 :     auto work = [this](progress::ProgressSession &session, tui::SyncTuiControl &control) -> Result {
     427           0 :         auto prev_logger = get_logger_if();
     428           0 :         if (control.logger) set_logger_if(control.logger);
     429             : 
     430           0 :         auto restore_logger = [&]() {
     431           0 :             if (prev_logger) set_logger_if(prev_logger);
     432           0 :         };
     433             : 
     434           0 :         if (control.set_phase) control.set_phase("Loading workspace…");
     435             : 
     436           0 :         Result phase = default_handling_folder_workspace();
     437           0 :         if (phase.error())
     438             :         {
     439           0 :             restore_logger();
     440             :             return phase;
     441             :         }
     442             : 
     443           0 :         phase = open_esysrepo_folder();
     444           0 :         if (phase.error())
     445             :         {
     446           0 :             restore_logger();
     447             :             return phase;
     448             :         }
     449             : 
     450           0 :         if (control.set_phase) control.set_phase("Loading manifest…");
     451             : 
     452           0 :         phase = sync_manifest();
     453           0 :         if (phase.error())
     454             :         {
     455           0 :             restore_logger();
     456             :             return phase;
     457             :         }
     458             : 
     459           0 :         phase = load_manifest();
     460           0 :         if (phase.error())
     461             :         {
     462           0 :             restore_logger();
     463             :             return phase;
     464             :         }
     465             : 
     466           0 :         phase = apply_revision_set_selection();
     467           0 :         if (phase.error())
     468             :         {
     469           0 :             restore_logger();
     470             :             return phase;
     471             :         }
     472             : 
     473           0 :         phase = apply_revision_pins();
     474           0 :         if (phase.error())
     475             :         {
     476           0 :             restore_logger();
     477             :             return phase;
     478             :         }
     479             : 
     480           0 :         manifest::SyncRepos sync_repos;
     481             : 
     482           0 :         if (!get_branch().empty()) sync_repos.set_branch(get_branch());
     483             : 
     484           0 :         if (get_sub_args().size() != 0)
     485           0 :             sync_repos.set_folders_to_sync(get_sub_args());
     486           0 :         else if (get_groups().size() != 0)
     487             :         {
     488           0 :             std::vector<std::string> repos;
     489           0 :             for (auto &group : get_groups())
     490             :             {
     491           0 :                 auto group_ptr = get_manifest()->get_groups().find_group_by_name(group);
     492           0 :                 if (group_ptr == nullptr) continue;
     493           0 :                 for (auto repo : group_ptr->get_repos()) repos.push_back(repo->get_path());
     494           0 :             }
     495           0 :             sync_repos.set_folders_to_sync(repos);
     496           0 :         }
     497             : 
     498           0 :         if (get_debug()) sync_repos.set_log_level(log::Level::DEBUG);
     499             : 
     500           0 :         sync_repos.set_job_count(get_job_count());
     501           0 :         sync_repos.set_logger_if(get_logger_if());
     502           0 :         sync_repos.set_config_folder(get_config_folder());
     503           0 :         sync_repos.set_git(get_git());
     504           0 :         sync_repos.set_manifest(get_manifest());
     505           0 :         sync_repos.set_tui(false);
     506           0 :         sync_repos.set_alt_address(get_alt_address());
     507           0 :         sync_repos.set_recurse_submodules(get_recurse_submodules());
     508             : 
     509           0 :         get_git()->detect_ssh_agent(true);
     510           0 :         if (control.logger) get_git()->set_logger_if(control.logger);
     511             : 
     512           0 :         phase = sync_repos.run_with_session(session, &control);
     513           0 :         restore_logger();
     514           0 :         return phase;
     515           0 :     };
     516             : 
     517           0 :     tui::SyncTuiOptions tui_options;
     518           0 :     tui_options.demo = get_tui_demo();
     519           0 :     result = tui::run_compact_sync_tui(work, {}, ui_workers, get_logger_if(), tui_options);
     520           0 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     521             : 
     522           0 :     if (!get_multi()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     523           0 :     return ETRC_RET(ESYSREPO_RESULT(do_multi()));
     524             : #endif
     525           0 : }
     526             : 
     527             : } // namespace esys::repo::exe

Generated by: LCOV version 1.14