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