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
|