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