Line data Source code
1 : /*! 2 : * \file esys/repo/gitbase.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/gitbase.h" 20 : #include "esys/repo/gitmngr.h" 21 : 22 : #include <esys/trace/call.h> 23 : #include <esys/trace/macros.h> 24 : 25 : #include <boost/filesystem.hpp> 26 : #include <boost/algorithm/string.hpp> 27 : 28 : #include <iostream> 29 : 30 : namespace esys::repo 31 : { 32 : 33 645 : GitBase::GitBase() 34 645 : : log::User() 35 : { 36 645 : } 37 : 38 653 : GitBase::~GitBase() = default; 39 : 40 1312 : void GitBase::set_author(std::shared_ptr<git::Person> person) 41 : { 42 1312 : m_person = person; 43 1312 : } 44 : 45 1323 : std::shared_ptr<git::Person> GitBase::get_author() const 46 : { 47 1323 : return m_person; 48 : } 49 : 50 1308 : void GitBase::set_committer(std::shared_ptr<git::Person> person) 51 : { 52 1308 : m_committer = person; 53 1308 : } 54 : 55 1315 : std::shared_ptr<git::Person> GitBase::get_committer() const 56 : { 57 1315 : return m_committer; 58 : } 59 : 60 0 : Result GitBase::worktree_add_detach(const std::string & /*path*/, const std::string & /*commit*/) 61 : { 62 0 : return ESYSREPO_RESULT(ResultCode::NOT_IMPLEMENTED); 63 : } 64 : 65 0 : Result GitBase::worktree_list(std::vector<git::Worktree> & /*worktrees*/) 66 : { 67 0 : return ESYSREPO_RESULT(ResultCode::NOT_IMPLEMENTED); 68 : } 69 : 70 0 : Result GitBase::worktree_remove(const std::string & /*path*/, bool /*force*/) 71 : { 72 0 : return ESYSREPO_RESULT(ResultCode::NOT_IMPLEMENTED); 73 : } 74 : 75 0 : Result_t<bool> GitBase::has_object(const std::string & /*commit*/) 76 : { 77 0 : return Result_t<bool>(ESYSREPO_RESULT(ResultCode::NOT_IMPLEMENTED)); 78 : } 79 : 80 0 : Result GitBase::list_gitlinks(std::map<std::string, std::string> & /*out*/, const std::string & /*rev*/) 81 : { 82 0 : return ESYSREPO_RESULT(ResultCode::NOT_IMPLEMENTED); 83 : } 84 : 85 0 : Result GitBase::list_index_gitlinks(std::map<std::string, std::string> & /*out*/) 86 : { 87 0 : return ESYSREPO_RESULT(ResultCode::NOT_IMPLEMENTED); 88 : } 89 : 90 2 : Result GitBase::reset_to_parent(int nth_parent) 91 : { 92 2 : int result = is_open(); 93 : 94 2 : if (result < 0) return ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN); 95 : 96 2 : git::CommitHash last_commit; 97 2 : git::CommitHash parent_commit; 98 : 99 : // Get the last commit in the manifest git repo 100 2 : Result rresult = get_last_commit(last_commit); 101 2 : if (rresult.error()) return ESYSREPO_RESULT(rresult); 102 : 103 : // Get the its parent commit 104 2 : rresult = get_parent_commit(last_commit, parent_commit); 105 2 : if (rresult.error()) return ESYSREPO_RESULT(rresult); 106 : 107 : // Reset the manifest git repo to the parent commit 108 2 : rresult = reset(parent_commit, git::ResetType::HARD); 109 2 : if (rresult.error()) return ESYSREPO_RESULT(rresult); 110 : 111 2 : git::CommitHash new_last_commit; 112 : 113 : // Get the new last commit 114 2 : rresult = get_last_commit(new_last_commit); 115 2 : if (rresult.error()) return ESYSREPO_RESULT(rresult); 116 : 117 : // The new last commit should be the parent commit 118 2 : if (parent_commit.get_hash() != new_last_commit.get_hash()) return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR); 119 2 : return ESYSREPO_RESULT(ResultCode::OK); 120 2 : } 121 : 122 436 : bool GitBase::is_repo(const std::string &path) 123 : { 124 436 : boost::filesystem::path git_path = path; 125 : 126 436 : git_path /= ".git"; 127 : 128 436 : return boost::filesystem::exists(git_path); 129 436 : } 130 : 131 1308 : void GitBase::set_id(std::size_t id) 132 : { 133 1308 : m_id = id; 134 1308 : } 135 : 136 1308 : std::size_t GitBase::get_id() const 137 : { 138 1308 : return m_id; 139 : } 140 : 141 1308 : void GitBase::set_debug(bool debug) 142 : { 143 1308 : m_debug = debug; 144 1308 : } 145 : 146 1308 : bool GitBase::get_debug() const 147 : { 148 1308 : return m_debug; 149 : } 150 : 151 17551 : int GitBase::handle_sideband_progress(const std::string &text) 152 : { 153 17551 : git::Progress progress; 154 17551 : int result = decode_sideband_progress(text, progress); 155 17551 : if (result != 0) return result; 156 : 157 : // Ignore non-progress chatter merged on stderr (e.g. "Cloning into..."). 158 17837 : if (progress.get_fetch_step() == git::FetchStep::NOT_SET && progress.get_percentage() < 0 159 17837 : && !progress.get_done() && !progress.get_started()) 160 : return 0; 161 : 162 17276 : notify_progress_throttled(progress); 163 : return 0; 164 : } 165 : 166 239 : int GitBase::handle_transfer_progress(const git::Progress &progress) 167 : { 168 239 : notify_progress_throttled(progress); 169 239 : return 0; 170 : } 171 : 172 18895 : void GitBase::notify_progress_throttled(const git::Progress &progress) 173 : { 174 : // Single-thread per GitBase for progress producers: throttle without locking. 175 18895 : constexpr auto k_min_emit_interval = std::chrono::milliseconds(50); 176 18895 : const auto now = std::chrono::steady_clock::now(); 177 18895 : const bool step_changed = progress.get_fetch_step() != m_last_emitted_progress_step; 178 18895 : const bool due = !m_progress_emit_armed || (now - m_last_progress_emit) >= k_min_emit_interval; 179 18895 : if (!(progress.get_started() || progress.get_done() || step_changed || due)) return; 180 : 181 2502 : m_progress_emit_armed = true; 182 2502 : m_last_progress_emit = now; 183 2502 : m_last_emitted_progress_step = progress.get_fetch_step(); 184 : 185 2502 : set_progress(progress); 186 2502 : if (m_progress_callback != nullptr) m_progress_callback->git_progress_notif(progress); 187 : } 188 : 189 2502 : void GitBase::set_progress(const git::Progress &progress) 190 : { 191 2502 : std::lock_guard lock(m_progress_mutex); 192 : 193 2502 : m_progress = progress; 194 2502 : } 195 : 196 0 : void GitBase::get_progress(git::Progress &progress) 197 : { 198 0 : std::lock_guard lock(m_progress_mutex); 199 : 200 0 : progress = m_progress; 201 0 : } 202 : 203 1461 : void GitBase::set_progress_callback(ProgressCallbackBase *progress_callback) 204 : { 205 1461 : m_progress_callback = progress_callback; 206 1461 : } 207 : 208 1308 : ProgressCallbackBase *GitBase::get_progress_callback() 209 : { 210 1308 : return m_progress_callback; 211 : } 212 : 213 0 : void GitBase::clear_notes_ref() 214 : { 215 0 : m_notes_references.clear(); 216 0 : m_map_notes_references.clear(); 217 0 : } 218 : 219 9 : int GitBase::add_notes_ref(const std::string ¬es_ref) 220 : { 221 9 : if (m_map_notes_references.find(notes_ref) != m_map_notes_references.end()) return -1; 222 : 223 9 : m_map_notes_references[notes_ref] = true; 224 9 : m_notes_references.push_back(notes_ref); 225 9 : return 0; 226 : } 227 : 228 10 : std::vector<std::string> &GitBase::get_notes_references() 229 : { 230 10 : return m_notes_references; 231 : } 232 : 233 0 : const std::vector<std::string> &GitBase::get_notes_references() const 234 : { 235 0 : return m_notes_references; 236 : } 237 : 238 17591 : int GitBase::decode_sideband_progress(const std::string &txt, git::Progress &progress) 239 : { 240 17591 : std::string txt_input = txt; 241 : 242 17591 : std::replace(txt_input.begin(), txt_input.end(), '\r', '\n'); 243 17591 : boost::to_lower(txt_input); 244 : 245 : // std::cout << "'" << txt_input << "'" << std::endl; 246 : 247 17591 : std::vector<std::string> lines; 248 17591 : boost::split(lines, txt_input, [](char c) { return c == '\n'; }); 249 : 250 17591 : std::size_t percentage_idx = std::string::npos; 251 17591 : progress.set_percentage(-1); 252 17591 : bool percentage_error = false; 253 17591 : progress.set_fetch_step(git::FetchStep::NOT_SET); 254 : 255 35834 : for (auto &line : lines) 256 : { 257 18243 : boost::trim(line); 258 : 259 18243 : if (line.empty()) continue; 260 : 261 18140 : progress.set_done(false); 262 18140 : progress.set_percentage(-1); 263 : 264 18140 : if (line.find("done") != line.npos) progress.set_done(true); 265 18140 : percentage_idx = line.find("%"); 266 : 267 18140 : if (percentage_idx != line.npos) 268 : { 269 17377 : std::string percentage_str = line.substr(0, percentage_idx); 270 17377 : std::size_t i = percentage_str.rfind(' '); 271 17377 : if (i != percentage_str.npos) 272 : { 273 17352 : percentage_str = percentage_str.substr(i + 1); 274 17352 : int result = 0; 275 17352 : percentage_error = false; 276 17352 : try 277 : { 278 17352 : result = std::stoi(percentage_str); 279 : } 280 0 : catch (std::invalid_argument &) 281 : { 282 0 : percentage_error = true; 283 0 : progress.set_percentage(-1); 284 0 : } 285 0 : catch (std::out_of_range &) 286 : { 287 0 : percentage_error = true; 288 0 : progress.set_percentage(-1); 289 0 : } 290 0 : if (percentage_error) 291 0 : progress.set_percentage(-1); 292 : else 293 17352 : progress.set_percentage(result); 294 : } 295 17377 : } 296 : 297 18140 : if (line.find("enumerating") != line.npos) 298 : { 299 184 : progress.set_fetch_step(git::FetchStep::ENUMERATING); 300 : } 301 17956 : else if (line.find("counting") != line.npos) 302 : { 303 4007 : progress.set_fetch_step(git::FetchStep::COUNTING); 304 : } 305 13949 : else if (line.find("compressing") != line.npos) 306 : { 307 2978 : progress.set_fetch_step(git::FetchStep::COMPRESSING); 308 : } 309 10971 : else if (line.find("receiving") != line.npos) 310 : { 311 6306 : progress.set_fetch_step(git::FetchStep::RECEIVING); 312 : } 313 4665 : else if (line.find("resolving") != line.npos) 314 : { 315 4058 : progress.set_fetch_step(git::FetchStep::RESOLVING); 316 : } 317 607 : else if (line.find("total") != line.npos) 318 : { 319 179 : progress.set_fetch_step(git::FetchStep::TOTAL); 320 : } 321 : 322 : // Optional " (cur/tot)" counters, e.g. Receiving objects: 45% (123/456) 323 18140 : const auto lparen = line.find('('); 324 18140 : const auto slash = (lparen == line.npos) ? line.npos : line.find('/', lparen); 325 17558 : const auto rparen = (slash == line.npos) ? line.npos : line.find(')', slash); 326 18140 : if (lparen != line.npos && slash != line.npos && rparen != line.npos && rparen > slash + 1) 327 : { 328 17363 : try 329 : { 330 34726 : const int cur = std::stoi(line.substr(lparen + 1, slash - lparen - 1)); 331 34726 : const int tot = std::stoi(line.substr(slash + 1, rparen - slash - 1)); 332 17363 : const auto step = progress.get_fetch_step(); 333 17363 : if (step == git::FetchStep::RESOLVING) 334 : { 335 4058 : progress.set_indexed_deltas(cur); 336 4058 : progress.set_total_deltas(tot); 337 : } 338 13305 : else if (step == git::FetchStep::RECEIVING || step == git::FetchStep::COUNTING 339 3034 : || step == git::FetchStep::COMPRESSING || step == git::FetchStep::ENUMERATING) 340 : { 341 13202 : progress.set_received_objects(cur); 342 13202 : progress.set_total_objects(tot); 343 13202 : if (step == git::FetchStep::RECEIVING) progress.set_indexed_objects(cur); 344 : } 345 : } 346 0 : catch (const std::exception &) 347 : { 348 : // Ignore malformed counters; percentage/step still useful. 349 0 : } 350 : } 351 : } 352 35182 : return 0; 353 17591 : } 354 : 355 735 : void GitBase::cmd_start() 356 : { 357 735 : ETRC_CALL_NP(); 358 : 359 735 : m_last_cmd_start_time = std::chrono::steady_clock::now(); 360 : 361 : // Cast the time point to ms, then get its duration, then get the duration's count. 362 735 : auto ms = std::chrono::time_point_cast<std::chrono::milliseconds>(m_last_cmd_start_time).time_since_epoch().count(); 363 735 : ETRC_MSG_OS("last_cmd_start_time : " << ms); 364 : 365 735 : git::Progress progress; 366 735 : progress.set_started(true); 367 735 : notify_progress_throttled(progress); 368 735 : } 369 : 370 814 : void GitBase::cmd_end() 371 : { 372 814 : ETRC_CALL_NP(); 373 : 374 814 : m_last_cmd_end_time = std::chrono::steady_clock::now(); 375 : 376 : // Cast the time point to ms, then get its duration, then get the duration's count. 377 814 : auto ms = std::chrono::time_point_cast<std::chrono::milliseconds>(m_last_cmd_end_time).time_since_epoch().count(); 378 814 : ETRC_MSG_OS("last_cmd_end_time : " << ms); 379 814 : } 380 : 381 978 : void GitBase::open_time() 382 : { 383 978 : m_open_time = std::chrono::steady_clock::now(); 384 978 : } 385 : 386 981 : void GitBase::close_time() 387 : { 388 981 : m_close_time = std::chrono::steady_clock::now(); 389 981 : } 390 : 391 0 : uint64_t GitBase::get_open_time() 392 : { 393 0 : if (m_close_time < m_open_time) return 0; 394 : 395 0 : auto d_milli = std::chrono::duration_cast<std::chrono::milliseconds>(m_close_time - m_open_time).count(); 396 : 397 0 : return static_cast<uint64_t>(d_milli); 398 : } 399 : 400 0 : uint64_t GitBase::get_last_cmd_elapsed_time() 401 : { 402 0 : if (m_last_cmd_end_time < m_last_cmd_start_time) return 0; 403 : 404 0 : auto d_milli = 405 0 : std::chrono::duration_cast<std::chrono::milliseconds>(m_last_cmd_end_time - m_last_cmd_start_time).count(); 406 : 407 0 : return static_cast<uint64_t>(d_milli); 408 : } 409 : 410 54 : uint64_t GitBase::take_last_refresh_ms() 411 : { 412 54 : return 0; 413 : } 414 : 415 75 : GitBase &GitBase::ssh_probe() 416 : { 417 75 : static std::shared_ptr<GitBase> instance = GitMngr::new_ptr(); 418 75 : return *instance; 419 : } 420 : 421 3 : bool GitBase::is_ssh_backend_supported(git::SshBackend backend) 422 : { 423 3 : return ssh_probe().do_is_ssh_backend_supported(backend); 424 : } 425 : 426 2 : bool GitBase::is_ssh_backend_available(git::SshBackend backend, bool force) 427 : { 428 2 : return ssh_probe().do_is_ssh_backend_available(backend, force); 429 : } 430 : 431 5 : Result GitBase::set_ssh_backend(git::SshBackend backend) 432 : { 433 5 : return ssh_probe().do_set_ssh_backend(backend); 434 : } 435 : 436 65 : Result_t<git::SshBackend> GitBase::get_ssh_backend() 437 : { 438 65 : return ssh_probe().do_get_ssh_backend(); 439 : } 440 : 441 : } // namespace esys::repo