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 30 : CmdSync::CmdSync() 60 60 : : Cmd("Sync") 61 : { 62 30 : } 63 : 64 30 : CmdSync::~CmdSync() = default; 65 : 66 0 : void CmdSync::set_force(bool force) 67 : { 68 0 : m_force = force; 69 0 : } 70 : 71 20 : bool CmdSync::get_force() const 72 : { 73 20 : 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 0 : void CmdSync::set_alt_address(bool alt_address) 87 : { 88 0 : m_alt_address = alt_address; 89 0 : } 90 0 : bool CmdSync::get_alt_address() const 91 : { 92 0 : return m_alt_address; 93 : } 94 : 95 0 : void CmdSync::set_multi(bool multi) 96 : { 97 0 : m_multi = multi; 98 0 : } 99 : 100 20 : bool CmdSync::get_multi() const 101 : { 102 20 : return m_multi; 103 : } 104 : 105 0 : void CmdSync::set_tui(bool tui) 106 : { 107 0 : m_tui = tui; 108 0 : } 109 : 110 20 : bool CmdSync::get_tui() const 111 : { 112 20 : return m_tui; 113 : } 114 : 115 0 : void CmdSync::set_tui_demo(bool tui_demo) 116 : { 117 0 : m_tui_demo = tui_demo; 118 0 : if (tui_demo) m_tui = true; 119 0 : } 120 : 121 0 : bool CmdSync::get_tui_demo() const 122 : { 123 0 : return m_tui_demo; 124 : } 125 : 126 20 : Result CmdSync::sync_manifest() 127 : { 128 20 : Result result; 129 20 : ETRC_CALL_RET_NP(result); 130 : 131 20 : manifest::Sync sync; 132 : 133 40 : if (get_config_folder() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::CMD_CONFIG_FOLDER_NULLPTR)); 134 : 135 20 : sync.set_config_folder(get_config_folder()); 136 20 : sync.set_git(get_git()); 137 20 : sync.set_logger_if(get_logger_if()); 138 20 : if (!get_branch().empty()) sync.set_branch(get_branch()); 139 : 140 40 : return ETRC_RET(ESYSREPO_RESULT(sync.run())); 141 20 : } 142 : 143 0 : Result CmdSync::handle_capture_item(std::shared_ptr<manifest::Capture::Item> item) 144 : { 145 0 : Result result; 146 0 : ETRC_CALL_RET(result, item); 147 : 148 0 : if (get_workspace_path().empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::CMD_WORKSPACE_PATH_IS_EMPTY)); 149 : 150 0 : boost::filesystem::path git_path_folder = get_workspace_path(); 151 0 : git_path_folder /= item->get_path(); 152 0 : git_path_folder = git_path_folder.lexically_normal().make_preferred(); 153 : 154 0 : auto git_helper = new_git_helper(); 155 0 : result = git_helper->open(git_path_folder.string(), log::Level::DEBUG); 156 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 157 : 158 0 : GitHelper::AutoClose auto_close(git_helper.get()); 159 : 160 0 : bool dirty = false; 161 0 : result = git_helper->is_dirty(dirty, log::Level::DEBUG); 162 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 163 : 164 : // If there is a remote defined, create it and fetch it 165 0 : if (!item->get_remote_name().empty()) 166 : { 167 0 : result = git_helper->get_git()->add_remote(item->get_remote_name(), item->get_remote_url()); 168 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 169 : 170 0 : result = git_helper->get_git()->fetch(item->get_remote_name()); 171 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 172 : } 173 : 174 : // Checkout the new revision 175 0 : result = git_helper->checkout(item->get_revision(), false, log::Level::DEBUG); 176 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 177 : 178 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK)); 179 0 : } 180 : 181 0 : Result CmdSync::do_multi() 182 : { 183 0 : Result result; 184 0 : ETRC_CALL_RET_NP(result); 185 : 186 : // First we need to get the working folder 187 0 : std::string folder_path; 188 : 189 0 : if (!get_folder().empty()) 190 0 : folder_path = get_folder(); 191 0 : else if (!get_workspace_path().empty()) 192 0 : folder_path = get_workspace_path(); 193 : else 194 0 : folder_path = boost::filesystem::current_path().string(); 195 0 : if (folder_path.empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR)); 196 : 197 0 : auto git_folder_path = find_git_root_path(folder_path); 198 0 : auto git_helper = new_git_helper(); 199 : 200 0 : set_git_folder_path(git_folder_path); 201 0 : result = git_helper->open(git_folder_path, log::Level::DEBUG); 202 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 203 : 204 0 : GitHelper::AutoClose auto_close(git_helper.get()); 205 : 206 0 : int result_int = git_helper->get_git()->add_notes_ref("esysrepo"); 207 0 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)); 208 : 209 0 : result = git_helper->get_git()->fetch_all_notes(); 210 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 211 : 212 0 : git::Commit last_commit; 213 : 214 0 : result = git_helper->get_git()->get_last_commit(last_commit, true); 215 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 216 : 217 0 : std::vector<std::shared_ptr<git::Note>> notes; 218 0 : result_int = last_commit.get_notes("esysrepo", notes); 219 0 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)); 220 : 221 0 : std::ostringstream oss; 222 : 223 0 : oss << "Found " << notes.size() << " esysrepo multi note."; 224 : 225 0 : info(oss.str()); 226 : 227 0 : if (notes.empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK)); 228 0 : if (notes.size() != 1) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR)); 229 : 230 0 : std::string note_text = notes[0]->get_message(); 231 0 : auto capture = std::make_shared<manifest::Capture>(); 232 0 : result = load_capture(capture, note_text); 233 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 234 : 235 0 : oss.str(""); 236 0 : print_capture(oss, capture); 237 0 : info(oss.str()); 238 : 239 0 : Result rresult; 240 0 : for (auto repo : capture->get_items()) 241 : { 242 0 : result = handle_capture_item(repo); 243 0 : if (result.error()) 244 : { 245 0 : std::ostringstream e_oss; 246 : 247 0 : e_oss << "repo '" << repo->get_path() << "' could set revivions '" << repo->get_revision() << "'"; 248 0 : error(e_oss.str()); 249 0 : rresult.add(result); 250 0 : } 251 0 : } 252 : 253 0 : return ETRC_RET(ESYSREPO_RESULT(rresult)); 254 0 : } 255 : 256 0 : void CmdSync::set_git_folder_path(const std::string &git_folder_path) 257 : { 258 0 : m_git_folder_path = git_folder_path; 259 0 : } 260 : 261 0 : const std::string &CmdSync::get_git_folder_path() const 262 : { 263 0 : return m_git_folder_path; 264 : } 265 : 266 20 : Result CmdSync::impl_run() 267 : { 268 20 : Result result; 269 20 : ETRC_CALL_RET_NP(result); 270 : 271 20 : if (env_flag_enabled("ESYSREPO_TUI_DEMO")) set_tui_demo(true); 272 : 273 20 : if (get_tui()) return ETRC_RET(ESYSREPO_RESULT(impl_run_tui())); 274 : 275 20 : if (get_force()) warn("Option --force-sync is not implemented yet"); 276 : 277 20 : result = default_handling_folder_workspace(); 278 20 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 279 : 280 20 : result = open_esysrepo_folder(); 281 20 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 282 : 283 20 : result = sync_manifest(); 284 20 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 285 : 286 20 : result = load_manifest(); 287 20 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 288 : 289 20 : manifest::SyncRepos sync_repos; 290 : 291 20 : if (!get_branch().empty()) sync_repos.set_branch(get_branch()); 292 : 293 20 : if (get_sub_args().size() != 0) 294 4 : sync_repos.set_folders_to_sync(get_sub_args()); 295 16 : else if (get_groups().size() != 0) 296 : { 297 2 : std::vector<std::string> repos; 298 : 299 4 : for (auto &group : get_groups()) 300 : { 301 2 : auto group_ptr = get_manifest()->get_groups().find_group_by_name(group); 302 2 : if (group_ptr == nullptr) 303 : { 304 : //! \TODO 305 0 : continue; 306 : } 307 8 : for (auto repo : group_ptr->get_repos()) 308 : { 309 6 : repos.push_back(repo->get_path()); 310 6 : } 311 2 : } 312 : 313 2 : sync_repos.set_folders_to_sync(repos); 314 2 : } 315 : 316 20 : if (get_debug()) sync_repos.set_log_level(log::Level::DEBUG); 317 : 318 20 : sync_repos.set_job_count(get_job_count()); 319 20 : sync_repos.set_logger_if(get_logger_if()); 320 20 : sync_repos.set_config_folder(get_config_folder()); 321 20 : sync_repos.set_git(get_git()); 322 20 : sync_repos.set_manifest(get_manifest()); 323 20 : sync_repos.set_tui(false); 324 : 325 20 : get_git()->detect_ssh_agent(true); 326 : 327 20 : result = sync_repos.run(); 328 20 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 329 : 330 40 : if (!get_multi()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK)); 331 0 : return ETRC_RET(ESYSREPO_RESULT(do_multi())); 332 20 : } 333 : 334 0 : Result CmdSync::impl_run_tui() 335 : { 336 0 : Result result; 337 0 : ETRC_CALL_RET_NP(result); 338 : 339 : #ifndef ESYSREPO_HAVE_TUI 340 : error("--tui requested but esysrepo was built without FTXUI (Conan with_tui / ESYSREPO_WITH_TUI)"); 341 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::NOT_IMPLEMENTED)); 342 : #else 343 0 : if (!tui::is_tty()) 344 : { 345 0 : const char *msg = 346 : "--tui requires a real Win32 console (cmd.exe, PowerShell, or Windows " 347 : "Terminal). Git Bash/mintty and redirected stdout are not supported."; 348 0 : error(msg); 349 0 : std::cerr << "ERROR: " << msg << std::endl; 350 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::GENERIC_ERROR)); 351 : } 352 : 353 0 : if (get_force()) warn("Option --force-sync is not implemented yet"); 354 : 355 0 : const std::size_t ui_workers = 356 0 : get_job_count() <= 0 ? std::size_t{1} : static_cast<std::size_t>(get_job_count()); 357 : 358 0 : auto work = [this](progress::ProgressSession &session, tui::SyncTuiControl &control) -> Result { 359 0 : auto prev_logger = get_logger_if(); 360 0 : if (control.logger) set_logger_if(control.logger); 361 : 362 0 : auto restore_logger = [&]() { 363 0 : if (prev_logger) set_logger_if(prev_logger); 364 0 : }; 365 : 366 0 : if (control.set_phase) control.set_phase("Loading workspace…"); 367 : 368 0 : Result phase = default_handling_folder_workspace(); 369 0 : if (phase.error()) 370 : { 371 0 : restore_logger(); 372 : return phase; 373 : } 374 : 375 0 : phase = open_esysrepo_folder(); 376 0 : if (phase.error()) 377 : { 378 0 : restore_logger(); 379 : return phase; 380 : } 381 : 382 0 : if (control.set_phase) control.set_phase("Loading manifest…"); 383 : 384 0 : phase = sync_manifest(); 385 0 : if (phase.error()) 386 : { 387 0 : restore_logger(); 388 : return phase; 389 : } 390 : 391 0 : phase = load_manifest(); 392 0 : if (phase.error()) 393 : { 394 0 : restore_logger(); 395 : return phase; 396 : } 397 : 398 0 : manifest::SyncRepos sync_repos; 399 : 400 0 : if (!get_branch().empty()) sync_repos.set_branch(get_branch()); 401 : 402 0 : if (get_sub_args().size() != 0) 403 0 : sync_repos.set_folders_to_sync(get_sub_args()); 404 0 : else if (get_groups().size() != 0) 405 : { 406 0 : std::vector<std::string> repos; 407 0 : for (auto &group : get_groups()) 408 : { 409 0 : auto group_ptr = get_manifest()->get_groups().find_group_by_name(group); 410 0 : if (group_ptr == nullptr) continue; 411 0 : for (auto repo : group_ptr->get_repos()) repos.push_back(repo->get_path()); 412 0 : } 413 0 : sync_repos.set_folders_to_sync(repos); 414 0 : } 415 : 416 0 : if (get_debug()) sync_repos.set_log_level(log::Level::DEBUG); 417 : 418 0 : sync_repos.set_job_count(get_job_count()); 419 0 : sync_repos.set_logger_if(get_logger_if()); 420 0 : sync_repos.set_config_folder(get_config_folder()); 421 0 : sync_repos.set_git(get_git()); 422 0 : sync_repos.set_manifest(get_manifest()); 423 0 : sync_repos.set_tui(false); 424 0 : sync_repos.set_alt_address(get_alt_address()); 425 : 426 0 : get_git()->detect_ssh_agent(true); 427 0 : if (control.logger) get_git()->set_logger_if(control.logger); 428 : 429 0 : phase = sync_repos.run_with_session(session, &control); 430 0 : restore_logger(); 431 0 : return phase; 432 0 : }; 433 : 434 0 : tui::SyncTuiOptions tui_options; 435 0 : tui_options.demo = get_tui_demo(); 436 0 : result = tui::run_compact_sync_tui(work, {}, ui_workers, get_logger_if(), tui_options); 437 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 438 : 439 0 : if (!get_multi()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK)); 440 0 : return ETRC_RET(ESYSREPO_RESULT(do_multi())); 441 : #endif 442 0 : } 443 : 444 : } // namespace esys::repo::exe