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