LCOV - code coverage report
Current view: top level - src/esys/repo/manifest - syncrepos_manifest.cpp (source / functions) Hit Total Coverage
Test: esysrepo_coverage.info Lines: 247 455 54.3 %
Date: 2026-11-07 22:18:26 Functions: 42 60 70.0 %

          Line data    Source code
       1             : /*!
       2             :  * \file esys/repo/manifest/syncrepos_manifest.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/manifest/syncrepos.h"
      20             : #include "esys/repo/manifest/location.h"
      21             : #include "esys/repo/gitmngr.h"
      22             : #include "esys/repo/githelper.h"
      23             : #include "esys/repo/gitphasetimings.h"
      24             : #include "esys/repo/tui/is_tty.h"
      25             : #include "esys/repo/tui/run_compact_sync_tui.h"
      26             : 
      27             : #include "esys/repo/filesystem.h"
      28             : 
      29             : #include <esys/repo/git/sshenv.h>
      30             : 
      31             : #include <esys/trace/call.h>
      32             : #include <esys/trace/macros.h>
      33             : 
      34             : #include <boost/filesystem.hpp>
      35             : 
      36             : #include <algorithm>
      37             : #include <cstdlib>
      38             : #include <iostream>
      39             : #include <set>
      40             : #include <sstream>
      41             : 
      42             : namespace esys::repo::manifest
      43             : {
      44             : 
      45          30 : SyncRepos::SyncRepos()
      46          30 :     : log::User()
      47             : {
      48             :     // ADR-0030: tip-at-clone is the SyncRepos product default.
      49          30 :     m_clone_options.checkout_rev = true;
      50          30 :     m_clone_options.single_branch = true;
      51          30 : }
      52             : 
      53         108 : SyncRepos::~SyncRepos() = default;
      54             : 
      55          26 : void SyncRepos::set_manifest(std::shared_ptr<Manifest> manifest)
      56             : {
      57          26 :     m_manifest = manifest;
      58          26 : }
      59             : 
      60         434 : std::shared_ptr<Manifest> SyncRepos::get_manifest() const
      61             : {
      62         434 :     return m_manifest;
      63             : }
      64             : 
      65          26 : void SyncRepos::set_config_folder(std::shared_ptr<ConfigFolder> config_folder)
      66             : {
      67          26 :     m_config_folder = config_folder;
      68          26 : }
      69             : 
      70         178 : std::shared_ptr<ConfigFolder> SyncRepos::get_config_folder()
      71             : {
      72         178 :     return m_config_folder;
      73             : }
      74             : 
      75          26 : void SyncRepos::set_git(std::shared_ptr<GitBase> git)
      76             : {
      77          26 :     m_git = git;
      78          26 : }
      79             : 
      80          51 : std::shared_ptr<GitBase> SyncRepos::get_git() const
      81             : {
      82          51 :     return m_git;
      83             : }
      84             : 
      85           0 : std::shared_ptr<GitBase> SyncRepos::get_git_or_new()
      86             : {
      87           0 :     if (get_git() != nullptr) return get_git();
      88             : 
      89           0 :     return new_git();
      90             : }
      91             : 
      92           0 : void SyncRepos::set_git_generator(GitBase::GeneratorType git_generator)
      93             : {
      94           0 :     m_git_generator = git_generator;
      95           0 : }
      96             : 
      97         304 : GitBase::GeneratorType SyncRepos::get_git_generator()
      98             : {
      99         304 :     return m_git_generator;
     100             : }
     101             : 
     102         152 : std::shared_ptr<GitBase> SyncRepos::new_git()
     103             : {
     104         152 :     if (get_git_generator() != nullptr) return get_git_generator()();
     105             : 
     106         152 :     return GitMngr::new_ptr();
     107             : }
     108             : 
     109          26 : bool SyncRepos::check_if_ssh_auth_needed_and_ok()
     110             : {
     111          26 :     bool result = false;
     112          26 :     ETRC_CALL_RET_NP(result);
     113             : 
     114          53 :     for (auto location : get_manifest()->get_locations())
     115             :     {
     116          35 :         for (auto repo : location->get_repos())
     117             :         {
     118         102 :             if (!is_repo_to_be_synced(repo)) continue;
     119             : 
     120          26 :             if (location->is_address_ssh())
     121             :             {
     122          25 :                 if (!get_alt_address()) return ETRC_RET(false);
     123           0 :                 if (location->is_alt_address_ssh()) return ETRC_RET(false);
     124             :             }
     125          34 :         }
     126          26 :     }
     127           1 :     return ETRC_RET(true);
     128          26 : }
     129             : 
     130          26 : Result SyncRepos::run()
     131             : {
     132          26 :     Result result;
     133          26 :     ETRC_CALL_RET_NP(result);
     134             : 
     135          52 :     if (get_manifest() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::MANIFEST_IS_NULLPTR));
     136          52 :     if (get_config_folder() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::CONFIG_FOLDER_IS_NULLPTR));
     137          52 :     if (get_git() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_IS_NULLPTR));
     138             : 
     139          26 :     apply_workspace_root_env();
     140             : 
     141          26 :     result = prepare_sharing_context();
     142          26 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     143             : 
     144         102 :     if (!check_if_ssh_auth_needed_and_ok() && !get_git()->is_ssh_agent_running()
     145          51 :         && git::explicit_ssh_private_key_from_env().empty())
     146           0 :         warn("SSH authentication needs an SSH agent, SSH_PRIVATE_KEY (file), or "
     147             :              "GIT_SSH_COMMAND -i. Git repos with SSH access may not sync.");
     148          26 :     bool display_repo_idx = true;
     149             : 
     150          26 :     if (get_run_tasks().get_job_count() == 1) display_repo_idx = false;
     151             : 
     152          26 :     std::vector<std::shared_ptr<SyncRepo>> root_tasks;
     153          26 :     std::vector<std::shared_ptr<SyncRepo>> other_tasks;
     154             : 
     155          26 :     set_repo_idx(0);
     156          84 :     for (auto location : get_manifest()->get_locations())
     157             :     {
     158         207 :         for (auto repo : location->get_repos())
     159             :         {
     160         525 :             if (!is_repo_to_be_synced(repo)) continue;
     161             : 
     162         304 :             std::shared_ptr<SyncRepo> sync_repo = make_sync_repo(repo, display_repo_idx);
     163         152 :             if (repo->get_path() == ".")
     164          20 :                 root_tasks.push_back(sync_repo);
     165             :             else
     166         132 :                 other_tasks.push_back(sync_repo);
     167             : 
     168         152 :             ++get_repo_idx();
     169         327 :         }
     170          32 :     }
     171             : 
     172          26 :     const std::size_t parallel_jobs = get_job_count();
     173             : 
     174          26 :     std::vector<std::shared_ptr<SyncRepo>> provider_tasks;
     175          26 :     std::vector<std::shared_ptr<SyncRepo>> non_provider_tasks;
     176          26 :     partition_provider_tasks(other_tasks, provider_tasks, non_provider_tasks);
     177             : 
     178          26 :     if (get_tui())
     179             :     {
     180             :         // Providers before consumers so worktree materialize can open the provider
     181           0 :         std::vector<std::shared_ptr<SyncRepo>> ordered = provider_tasks;
     182           0 :         ordered.insert(ordered.end(), non_provider_tasks.begin(), non_provider_tasks.end());
     183           0 :         result = run_sync_with_tui(root_tasks, ordered, parallel_jobs);
     184           0 :         if (!result.error())
     185             :         {
     186           0 :             Result fin = finalize_sharing();
     187           0 :             if (fin.error()) result = fin;
     188           0 :         }
     189           0 :         return ETRC_RET(ESYSREPO_RESULT(result));
     190           0 :     }
     191             : 
     192          52 :     auto run_root = [this, &root_tasks]() { return run_sync_tasks(root_tasks, 1); };
     193          52 :     auto run_providers = [this, &provider_tasks, parallel_jobs]() {
     194          26 :         return run_sync_tasks(provider_tasks, parallel_jobs);
     195          26 :     };
     196          52 :     auto run_non_providers = [this, &non_provider_tasks, parallel_jobs]() {
     197          26 :         return run_sync_tasks(non_provider_tasks, parallel_jobs);
     198          26 :     };
     199             : 
     200          26 :     if (get_workspace_root_order() == WorkspaceRootOrder::Last)
     201             :     {
     202           0 :         result = run_providers();
     203           0 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     204           0 :         result = run_non_providers();
     205           0 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     206           0 :         result = run_root();
     207             :     }
     208             :     else
     209             :     {
     210          26 :         result = run_root();
     211          26 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     212          26 :         result = run_providers();
     213          26 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     214          26 :         result = run_non_providers();
     215             :     }
     216             : 
     217          26 :     if (!result.error())
     218             :     {
     219          26 :         Result fin = finalize_sharing();
     220          26 :         if (fin.error()) result = fin;
     221          26 :     }
     222             : 
     223          52 :     return ETRC_RET(ESYSREPO_RESULT(result));
     224          26 : }
     225             : 
     226          28 : void SyncRepos::apply_workspace_root_env()
     227             : {
     228          28 :     if (const char *order_env = std::getenv("ESYSREPO_WORKSPACE_ROOT_ORDER"))
     229             :     {
     230           0 :         WorkspaceRootOrder order = m_workspace_root_order;
     231           0 :         if (parse_workspace_root_order(order_env, order))
     232             :         {
     233           0 :             m_workspace_root_order = order;
     234           0 :             std::ostringstream oss;
     235           0 :             oss << "Workspace root order: " << order << " (ESYSREPO_WORKSPACE_ROOT_ORDER)";
     236           0 :             info(oss.str());
     237           0 :         }
     238             :         else
     239           0 :             warn(std::string("Unknown ESYSREPO_WORKSPACE_ROOT_ORDER='") + order_env + "' (use first|last)");
     240             :     }
     241             : 
     242          28 :     if (const char *clone_env = std::getenv("ESYSREPO_WORKSPACE_ROOT_CLONE"))
     243             :     {
     244           2 :         WorkspaceRootCloneMethod method = m_workspace_root_clone_method;
     245           2 :         if (parse_workspace_root_clone_method(clone_env, method))
     246             :         {
     247           2 :             m_workspace_root_clone_method = method;
     248           2 :             std::ostringstream oss;
     249           2 :             oss << "Workspace root clone: " << method << " (ESYSREPO_WORKSPACE_ROOT_CLONE)";
     250           2 :             info(oss.str());
     251           2 :         }
     252             :         else
     253           0 :             warn(std::string("Unknown ESYSREPO_WORKSPACE_ROOT_CLONE='") + clone_env
     254           0 :                  + "' (use temp-move|init-fetch)");
     255             :     }
     256             : 
     257          28 :     if (const char *checkout_env = std::getenv("ESYSREPO_CLONE_CHECKOUT_REV"))
     258             :     {
     259           2 :         if (checkout_env[0] == '\0')
     260             :         {
     261             :             // Unset / empty: keep SyncRepos product default (tip).
     262             :         }
     263             :         else
     264             :         {
     265           2 :             std::string text = checkout_env;
     266          12 :             for (char &c : text)
     267             :             {
     268          10 :                 if (c >= 'A' && c <= 'Z') c = static_cast<char>(c - 'A' + 'a');
     269             :             }
     270           8 :             if (text == "1" || text == "true" || text == "yes" || text == "tip")
     271             :             {
     272           1 :                 m_clone_options.checkout_rev = true;
     273           1 :                 m_clone_options.single_branch = true;
     274           2 :                 info("Clone checkout_rev=true single_branch=true (ESYSREPO_CLONE_CHECKOUT_REV)");
     275             :             }
     276           4 :             else if (text == "0" || text == "false" || text == "no" || text == "default")
     277             :             {
     278           1 :                 m_clone_options.checkout_rev = false;
     279           1 :                 m_clone_options.single_branch = false;
     280           2 :                 info("Clone checkout_rev=false (ESYSREPO_CLONE_CHECKOUT_REV) — legacy path");
     281             :             }
     282             :             else
     283           0 :                 warn(std::string("Unknown ESYSREPO_CLONE_CHECKOUT_REV='") + checkout_env
     284           0 :                      + "' (use tip|default or 1|0)");
     285           2 :         }
     286             :     }
     287          28 : }
     288             : 
     289         152 : std::shared_ptr<SyncRepo> SyncRepos::make_sync_repo(std::shared_ptr<manifest::Repository> repo, bool display_repo_idx)
     290             : {
     291         152 :     std::shared_ptr<SyncRepo> sync_repo = std::make_shared<SyncRepo>();
     292             : 
     293         304 :     sync_repo->set_repo(repo);
     294         152 :     sync_repo->set_manifest(get_manifest());
     295         152 :     sync_repo->set_repo_idx(get_repo_idx());
     296         152 :     sync_repo->set_display_repo_idx(display_repo_idx);
     297         152 :     sync_repo->set_config_folder(get_config_folder());
     298         152 :     sync_repo->set_git(new_git());
     299         304 :     sync_repo->get_git()->set_logger_if(get_logger_if());
     300         152 :     sync_repo->set_logger_if(get_logger_if());
     301         152 :     sync_repo->set_workspace_root_clone_method(get_workspace_root_clone_method());
     302         152 :     sync_repo->set_clone_options(get_clone_options());
     303             : 
     304         152 :     if (!get_branch().empty()) sync_repo->set_branch(get_branch());
     305             : 
     306         152 :     sync_repo->set_alt_address(get_alt_address());
     307             : 
     308         152 :     const auto *manifest = get_manifest().get();
     309         152 :     const bool update_subs =
     310             :         (manifest != nullptr)
     311         152 :             ? should_update_submodules(get_recurse_submodules(), *manifest, repo.get())
     312           0 :             : (get_recurse_submodules() == RecurseSubmodules::Yes);
     313         152 :     sync_repo->set_update_submodules(update_subs);
     314             : 
     315         152 :     sync_repo->set_sharing_context(m_sharing_ctx);
     316         152 :     sync_repo->set_git_generator(get_git_generator() != nullptr ? get_git_generator() : &GitMngr::new_ptr);
     317             : 
     318         152 :     return sync_repo;
     319           0 : }
     320             : 
     321          78 : Result SyncRepos::run_sync_tasks(const std::vector<std::shared_ptr<SyncRepo>> &tasks, std::size_t job_count)
     322             : {
     323          78 :     Result result;
     324          78 :     ETRC_CALL_RET_NP(result);
     325             : 
     326         111 :     if (tasks.empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     327             : 
     328          45 :     RunTasks run_tasks;
     329          45 :     run_tasks.set_logger_if(get_logger_if());
     330          45 :     run_tasks.set_job_count(job_count);
     331         349 :     for (const auto &task : tasks) run_tasks.add(task, task->get_repo_idx());
     332             : 
     333          45 :     result = run_tasks.run();
     334          45 :     log_phase_totals(tasks);
     335             : 
     336          90 :     return ETRC_RET(ESYSREPO_RESULT(result));
     337          78 : }
     338             : 
     339          45 : void SyncRepos::log_phase_totals(const std::vector<std::shared_ptr<SyncRepo>> &tasks)
     340             : {
     341          45 :     if (!GitHelper::phase_timing_enabled()) return;
     342             : 
     343           0 :     GitPhaseTimings totals;
     344           0 :     for (const auto &task : tasks) totals.add(task->get_phase_timings());
     345           0 :     if (totals.total_ms() != 0 || totals.refresh_ms != 0) info(totals.format("sync phase totals (ms):"));
     346             : }
     347             : 
     348           0 : Result SyncRepos::run_with_session(progress::ProgressSession &session, tui::SyncTuiControl *control)
     349             : {
     350           0 :     Result result;
     351           0 :     ETRC_CALL_RET_NP(result);
     352             : 
     353           0 :     if (get_manifest() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::MANIFEST_IS_NULLPTR));
     354           0 :     if (get_config_folder() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::CONFIG_FOLDER_IS_NULLPTR));
     355           0 :     if (get_git() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_IS_NULLPTR));
     356             : 
     357           0 :     apply_workspace_root_env();
     358             : 
     359           0 :     result = prepare_sharing_context();
     360           0 :     if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     361             : 
     362           0 :     if (!check_if_ssh_auth_needed_and_ok() && !get_git()->is_ssh_agent_running()
     363           0 :         && git::explicit_ssh_private_key_from_env().empty())
     364           0 :         warn("SSH authentication needs an SSH agent, SSH_PRIVATE_KEY (file), or "
     365             :              "GIT_SSH_COMMAND -i. Git repos with SSH access may not sync.");
     366             : 
     367           0 :     bool display_repo_idx = true;
     368           0 :     if (get_run_tasks().get_job_count() == 1) display_repo_idx = false;
     369             : 
     370           0 :     std::vector<std::shared_ptr<SyncRepo>> root_tasks;
     371           0 :     std::vector<std::shared_ptr<SyncRepo>> other_tasks;
     372             : 
     373           0 :     set_repo_idx(0);
     374           0 :     for (auto location : get_manifest()->get_locations())
     375             :     {
     376           0 :         for (auto repo : location->get_repos())
     377             :         {
     378           0 :             if (!is_repo_to_be_synced(repo)) continue;
     379             : 
     380           0 :             std::shared_ptr<SyncRepo> sync_repo = make_sync_repo(repo, display_repo_idx);
     381           0 :             if (repo->get_path() == ".")
     382           0 :                 root_tasks.push_back(sync_repo);
     383             :             else
     384           0 :                 other_tasks.push_back(sync_repo);
     385             : 
     386           0 :             ++get_repo_idx();
     387           0 :         }
     388           0 :     }
     389             : 
     390           0 :     const std::size_t parallel_jobs = get_job_count();
     391             : 
     392           0 :     std::vector<std::shared_ptr<SyncRepo>> provider_tasks;
     393           0 :     std::vector<std::shared_ptr<SyncRepo>> non_provider_tasks;
     394           0 :     partition_provider_tasks(other_tasks, provider_tasks, non_provider_tasks);
     395             : 
     396           0 :     auto task_name = [](const std::shared_ptr<SyncRepo> &task) -> std::string {
     397           0 :         std::string name;
     398           0 :         if (task->get_repo() != nullptr)
     399             :         {
     400           0 :             name = task->get_repo()->get_path();
     401           0 :             if (name.empty()) name = task->get_repo()->get_name();
     402             :         }
     403           0 :         if (name.empty()) name = "repo";
     404           0 :         return name;
     405           0 :     };
     406             : 
     407           0 :     std::vector<std::string> names;
     408           0 :     auto note_task = [&](const std::shared_ptr<SyncRepo> &task) {
     409           0 :         const std::size_t idx = task->get_repo_idx();
     410           0 :         if (names.size() <= idx) names.resize(idx + 1, "repo");
     411           0 :         names[idx] = task_name(task);
     412           0 :     };
     413           0 :     for (const auto &task : root_tasks) note_task(task);
     414           0 :     for (const auto &task : provider_tasks) note_task(task);
     415           0 :     for (const auto &task : non_provider_tasks) note_task(task);
     416             : 
     417           0 :     const std::size_t ui_workers_src =
     418           0 :         provider_tasks.size() + non_provider_tasks.size();
     419           0 :     const std::size_t ui_workers =
     420           0 :         ui_workers_src == 0 ? 1
     421           0 :                             : (std::min)(parallel_jobs == 0 ? std::size_t{1} : parallel_jobs, ui_workers_src);
     422             : 
     423           0 :     if (control != nullptr)
     424             :     {
     425           0 :         if (control->reset_repos) control->reset_repos(names, ui_workers);
     426           0 :         if (control->set_phase) control->set_phase("Sync in progress…");
     427             :     }
     428             : 
     429           0 :     if (names.empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     430             : 
     431           0 :     auto run_batch = [this](const std::vector<std::shared_ptr<SyncRepo>> &tasks, std::size_t job_count,
     432           0 :                             progress::ProgressSession &sess) -> Result {
     433           0 :         if (tasks.empty()) return ESYSREPO_RESULT(ResultCode::OK);
     434             : 
     435           0 :         RunTasks run_tasks;
     436           0 :         run_tasks.set_logger_if(get_logger_if());
     437           0 :         run_tasks.set_job_count(job_count);
     438           0 :         run_tasks.set_progress_session(&sess);
     439           0 :         run_tasks.set_console_progress(false);
     440           0 :         for (const auto &task : tasks) run_tasks.add(task, task->get_repo_idx());
     441           0 :         auto batch_result = run_tasks.run();
     442           0 :         log_phase_totals(tasks);
     443           0 :         return batch_result;
     444           0 :     };
     445             : 
     446           0 :     if (get_workspace_root_order() == WorkspaceRootOrder::Last)
     447             :     {
     448           0 :         result = run_batch(provider_tasks, parallel_jobs, session);
     449           0 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     450           0 :         result = run_batch(non_provider_tasks, parallel_jobs, session);
     451           0 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     452           0 :         result = run_batch(root_tasks, 1, session);
     453             :     }
     454             :     else
     455             :     {
     456           0 :         result = run_batch(root_tasks, 1, session);
     457           0 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     458           0 :         result = run_batch(provider_tasks, parallel_jobs, session);
     459           0 :         if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
     460           0 :         result = run_batch(non_provider_tasks, parallel_jobs, session);
     461             :     }
     462             : 
     463           0 :     if (!result.error())
     464             :     {
     465           0 :         Result fin = finalize_sharing();
     466           0 :         if (fin.error()) result = fin;
     467           0 :     }
     468             : 
     469           0 :     return ETRC_RET(ESYSREPO_RESULT(result));
     470           0 : }
     471             : 
     472           0 : Result SyncRepos::run_sync_with_tui(const std::vector<std::shared_ptr<SyncRepo>> &root_tasks,
     473             :                                    const std::vector<std::shared_ptr<SyncRepo>> &other_tasks,
     474             :                                    std::size_t parallel_jobs)
     475             : {
     476           0 :     Result result;
     477           0 :     ETRC_CALL_RET_NP(result);
     478             : 
     479             : #ifndef ESYSREPO_HAVE_TUI
     480             :     error("--tui requested but esysrepo was built without FTXUI (Conan with_tui / ESYSREPO_WITH_TUI)");
     481             :     return ETRC_RET(ESYSREPO_RESULT(ResultCode::NOT_IMPLEMENTED));
     482             : #else
     483           0 :     if (!tui::is_tty())
     484             :     {
     485           0 :         const char *msg =
     486             :             "--tui requires a real Win32 console (cmd.exe, PowerShell, or Windows "
     487             :             "Terminal). Git Bash/mintty and redirected stdout are not supported.";
     488           0 :         error(msg);
     489           0 :         std::cerr << "ERROR: " << msg << std::endl;
     490           0 :         return ETRC_RET(ESYSREPO_RESULT(ResultCode::GENERIC_ERROR));
     491             :     }
     492             : 
     493           0 :     auto task_name = [](const std::shared_ptr<SyncRepo> &task) -> std::string {
     494           0 :         std::string name;
     495           0 :         if (task->get_repo() != nullptr)
     496             :         {
     497           0 :             name = task->get_repo()->get_path();
     498           0 :             if (name.empty()) name = task->get_repo()->get_name();
     499             :         }
     500           0 :         if (name.empty()) name = "repo";
     501           0 :         return name;
     502           0 :     };
     503             : 
     504           0 :     std::vector<std::string> names;
     505           0 :     auto note_task = [&](const std::shared_ptr<SyncRepo> &task) {
     506           0 :         const std::size_t idx = task->get_repo_idx();
     507           0 :         if (names.size() <= idx) names.resize(idx + 1, "repo");
     508           0 :         names[idx] = task_name(task);
     509           0 :     };
     510           0 :     for (const auto &task : root_tasks) note_task(task);
     511           0 :     for (const auto &task : other_tasks) note_task(task);
     512             : 
     513           0 :     if (names.empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
     514             : 
     515           0 :     const std::size_t ui_workers =
     516           0 :         other_tasks.empty() ? 1 : (std::min)(parallel_jobs == 0 ? std::size_t{1} : parallel_jobs, other_tasks.size());
     517             : 
     518           0 :     auto run_batch = [this](const std::vector<std::shared_ptr<SyncRepo>> &tasks, std::size_t job_count,
     519           0 :                             progress::ProgressSession &session) -> Result {
     520           0 :         if (tasks.empty()) return ESYSREPO_RESULT(ResultCode::OK);
     521             : 
     522           0 :         RunTasks run_tasks;
     523           0 :         run_tasks.set_logger_if(get_logger_if());
     524           0 :         run_tasks.set_job_count(job_count);
     525           0 :         run_tasks.set_progress_session(&session);
     526           0 :         run_tasks.set_console_progress(false);
     527           0 :         for (const auto &task : tasks) run_tasks.add(task, task->get_repo_idx());
     528           0 :         auto batch_result = run_tasks.run();
     529           0 :         log_phase_totals(tasks);
     530           0 :         return batch_result;
     531           0 :     };
     532             : 
     533           0 :     auto work = [this, &root_tasks, &other_tasks, parallel_jobs, &run_batch](
     534           0 :                     progress::ProgressSession &session, tui::SyncTuiControl &control) -> Result {
     535           0 :         if (control.logger) set_logger_if(control.logger);
     536           0 :         if (control.set_phase) control.set_phase("Sync in progress…");
     537             : 
     538           0 :         Result phase;
     539           0 :         if (get_workspace_root_order() == WorkspaceRootOrder::Last)
     540             :         {
     541           0 :             phase = run_batch(other_tasks, parallel_jobs, session);
     542           0 :             if (phase.error()) return phase;
     543           0 :             return run_batch(root_tasks, 1, session);
     544             :         }
     545           0 :         phase = run_batch(root_tasks, 1, session);
     546           0 :         if (phase.error()) return phase;
     547           0 :         return run_batch(other_tasks, parallel_jobs, session);
     548           0 :     };
     549             : 
     550           0 :     result = tui::run_compact_sync_tui(work, names, ui_workers, get_logger_if());
     551           0 :     return ETRC_RET(ESYSREPO_RESULT(result));
     552             : #endif
     553           0 : }
     554             : 
     555          21 : void SyncRepos::set_log_level(log::Level log_level)
     556             : {
     557          21 :     m_log_level = log_level;
     558          21 : }
     559             : 
     560           0 : log::Level SyncRepos::get_log_level()
     561             : {
     562           0 :     return m_log_level;
     563             : }
     564             : 
     565          26 : void SyncRepos::set_repo_idx(std::size_t repo_idx)
     566             : {
     567          26 :     m_repo_idx = repo_idx;
     568          26 : }
     569             : 
     570           0 : std::size_t SyncRepos::get_repo_idx() const
     571             : {
     572           0 :     return m_repo_idx;
     573             : }
     574             : 
     575         304 : std::size_t &SyncRepos::get_repo_idx()
     576             : {
     577         304 :     return m_repo_idx;
     578             : }
     579             : 
     580          72 : bool SyncRepos::globly_match(const std::string &text, const std::string &glob)
     581             : {
     582          72 :     const char *text_backup = nullptr;
     583          72 :     const char *glob_backup = nullptr;
     584          72 :     const char *text_ = text.c_str();
     585          72 :     const char *glob_ = glob.c_str();
     586             : 
     587         384 :     while (*text_ != '\0')
     588             :     {
     589         371 :         if (*glob_ == '*')
     590             :         {
     591             :             // new star-loop: backup positions in pattern and text
     592          11 :             text_backup = text_;
     593          11 :             glob_backup = ++glob_;
     594             :         }
     595         360 :         else if ((*glob_ == '?' && *text_ != '/') || *glob_ == *text_)
     596             :         {
     597             :             // ? matched any character except /, or we matched the current non-NUL character
     598         263 :             text_++;
     599         263 :             glob_++;
     600             :         }
     601             :         else
     602             :         {
     603          97 :             if (glob_backup == nullptr || *text_backup == '/') return false;
     604             :             // star-loop: backtrack to the last * but do not jump over /
     605          38 :             text_ = ++text_backup;
     606          38 :             glob_ = glob_backup;
     607             :         }
     608             :     }
     609             :     // ignore trailing stars
     610          13 :     while (*glob_ == '*') glob_++;
     611             :     // at end of text means success if nothing else is left to match
     612          13 :     return *glob_ == '\0' ? true : false;
     613             : }
     614             : 
     615         224 : bool SyncRepos::is_repo_to_be_synced(std::shared_ptr<manifest::Repository> repo) const
     616             : {
     617         224 :     bool result = false;
     618         224 :     ETRC_CALL_RET(result, repo);
     619             : 
     620         224 :     if (m_map_folders_to_sync.size() == 0) return ETRC_RET(true);
     621             : 
     622          65 :     const std::string repo_path = generic_repo_path(repo->get_path());
     623             : 
     624          65 :     auto it = m_map_folders_to_sync.find(repo_path);
     625             : 
     626          65 :     if (it != m_map_folders_to_sync.end()) return ETRC_RET(true);
     627             : 
     628          45 :     result = false;
     629             :     // Now let's see if glob filter were used in the sync command
     630         105 :     for (auto &str : get_folders_to_sync())
     631             :     {
     632          67 :         result = globly_match(repo_path, str);
     633          67 :         if (result) return ETRC_RET(true);
     634             :     }
     635          38 :     return ETRC_RET(false);
     636         224 : }
     637             : 
     638           9 : void SyncRepos::set_folders_to_sync(std::vector<std::string> folders_to_sync)
     639             : {
     640           9 :     m_folders_to_sync.clear();
     641           9 :     m_map_folders_to_sync.clear();
     642             : 
     643          23 :     for (auto &folder : folders_to_sync)
     644             :     {
     645          14 :         const std::string norm = generic_repo_path(folder);
     646          14 :         m_folders_to_sync.push_back(norm);
     647          14 :         m_map_folders_to_sync[norm] = true;
     648          14 :     }
     649           9 : }
     650             : 
     651          45 : const std::vector<std::string> &SyncRepos::get_folders_to_sync() const
     652             : {
     653          45 :     return m_folders_to_sync;
     654             : }
     655             : 
     656          26 : void SyncRepos::set_job_count(std::size_t job_count)
     657             : {
     658          26 :     m_run_tasks.set_job_count(job_count);
     659          26 : }
     660             : 
     661          26 : std::size_t SyncRepos::get_job_count() const
     662             : {
     663          26 :     return m_run_tasks.get_job_count();
     664             : }
     665             : 
     666           5 : void SyncRepos::set_branch(const std::string &branch)
     667             : {
     668           5 :     m_branch = branch;
     669           5 : }
     670             : 
     671         184 : const std::string &SyncRepos::get_branch() const
     672             : {
     673         184 :     return m_branch;
     674             : }
     675             : 
     676           0 : void SyncRepos::set_alt_address(bool alt_address)
     677             : {
     678           0 :     m_alt_address = alt_address;
     679           0 : }
     680             : 
     681         177 : bool SyncRepos::get_alt_address() const
     682             : {
     683         177 :     return m_alt_address;
     684             : }
     685             : 
     686          26 : RunTasks &SyncRepos::get_run_tasks()
     687             : {
     688          26 :     return m_run_tasks;
     689             : }
     690             : 
     691           0 : const RunTasks &SyncRepos::get_run_tasks() const
     692             : {
     693           0 :     return m_run_tasks;
     694             : }
     695             : 
     696           0 : void SyncRepos::set_workspace_root_order(WorkspaceRootOrder order)
     697             : {
     698           0 :     m_workspace_root_order = order;
     699           0 : }
     700             : 
     701          26 : WorkspaceRootOrder SyncRepos::get_workspace_root_order() const
     702             : {
     703          26 :     return m_workspace_root_order;
     704             : }
     705             : 
     706           0 : void SyncRepos::set_workspace_root_clone_method(WorkspaceRootCloneMethod method)
     707             : {
     708           0 :     m_workspace_root_clone_method = method;
     709           0 : }
     710             : 
     711         152 : WorkspaceRootCloneMethod SyncRepos::get_workspace_root_clone_method() const
     712             : {
     713         152 :     return m_workspace_root_clone_method;
     714             : }
     715             : 
     716           1 : void SyncRepos::set_clone_options(const git::CloneOptions &options)
     717             : {
     718           1 :     m_clone_options = options;
     719           1 : }
     720             : 
     721         159 : const git::CloneOptions &SyncRepos::get_clone_options() const
     722             : {
     723         159 :     return m_clone_options;
     724             : }
     725             : 
     726          26 : void SyncRepos::set_recurse_submodules(RecurseSubmodules recurse)
     727             : {
     728          26 :     m_recurse_submodules = recurse;
     729          26 : }
     730             : 
     731         152 : RecurseSubmodules SyncRepos::get_recurse_submodules() const
     732             : {
     733         152 :     return m_recurse_submodules;
     734             : }
     735             : 
     736          26 : void SyncRepos::set_tui(bool tui)
     737             : {
     738          26 :     m_tui = tui;
     739          26 : }
     740             : 
     741          26 : bool SyncRepos::get_tui() const
     742             : {
     743          26 :     return m_tui;
     744             : }
     745             : 
     746          26 : Result SyncRepos::prepare_sharing_context()
     747             : {
     748          26 :     m_sharing_ctx.reset();
     749          52 :     if (get_manifest() == nullptr) return ESYSREPO_RESULT(ResultCode::OK);
     750             : 
     751          26 :     auto ctx = std::make_shared<sharing::Context>();
     752          26 :     Result r = ctx->build(*get_manifest());
     753          26 :     if (r.error()) return ESYSREPO_RESULT(r);
     754          26 :     if (ctx->has_providers()) m_sharing_ctx = ctx;
     755          26 :     return ESYSREPO_RESULT(ResultCode::OK);
     756          52 : }
     757             : 
     758          26 : Result SyncRepos::finalize_sharing()
     759             : {
     760          26 :     if (!m_sharing_ctx) return ESYSREPO_RESULT(ResultCode::OK);
     761           0 :     if (get_config_folder() == nullptr) return ESYSREPO_RESULT(ResultCode::CONFIG_FOLDER_IS_NULLPTR);
     762             : 
     763           0 :     if (get_folders_to_sync().empty()) m_sharing_ctx->get_inventory().set_coverage(sharing::Coverage::Complete);
     764             : 
     765           0 :     return sharing::finalize_inventory(*m_sharing_ctx, *get_config_folder(), get_logger_if());
     766             : }
     767             : 
     768          26 : void SyncRepos::partition_provider_tasks(const std::vector<std::shared_ptr<SyncRepo>> &other_tasks,
     769             :                                          std::vector<std::shared_ptr<SyncRepo>> &provider_tasks,
     770             :                                          std::vector<std::shared_ptr<SyncRepo>> &non_provider_tasks) const
     771             : {
     772          26 :     provider_tasks.clear();
     773          26 :     non_provider_tasks.clear();
     774          26 :     if (!m_sharing_ctx || !m_sharing_ctx->has_providers())
     775             :     {
     776          26 :         non_provider_tasks = other_tasks;
     777          26 :         return;
     778             :     }
     779             : 
     780           0 :     std::set<std::string> provider_paths;
     781           0 :     for (const auto &p : m_sharing_ctx->get_registry().get_providers())
     782             :     {
     783           0 :         if (p.get_repository()) provider_paths.insert(p.get_repository()->get_path());
     784             :     }
     785             : 
     786           0 :     for (const auto &task : other_tasks)
     787             :     {
     788           0 :         if (task && task->get_repo() && provider_paths.count(task->get_repo()->get_path()) != 0)
     789           0 :             provider_tasks.push_back(task);
     790             :         else
     791           0 :             non_provider_tasks.push_back(task);
     792             :     }
     793           0 : }
     794             : 
     795             : } // namespace esys::repo::manifest

Generated by: LCOV version 1.14