Line data Source code
1 : /*!
2 : * \file esys/repo/hybrid/git_hybrid.cpp
3 : * \brief Hybrid Git backend: gitcmdline clone/fetch + libgit2 elsewhere
4 : *
5 : * \cond
6 : * __legal_b__
7 : *
8 : * Copyright (c) 2026 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/hybrid/git.h"
20 : #include "esys/repo/gitcmdline/git.h"
21 : #include "esys/repo/libgit2/git.h"
22 :
23 : #include <chrono>
24 :
25 : namespace esys::repo::hybrid
26 : {
27 :
28 185 : Git::Git()
29 185 : : GitBase()
30 : {
31 185 : m_cmdline = std::make_shared<gitcmdline::Git>();
32 185 : m_libgit2 = std::make_shared<libgit2::Git>();
33 185 : }
34 :
35 185 : Git::~Git()
36 : {
37 185 : if (is_open()) close();
38 555 : }
39 :
40 183 : std::shared_ptr<GitBase> Git::new_ptr()
41 : {
42 183 : return std::make_shared<Git>();
43 : }
44 :
45 654 : void Git::sync_meta()
46 : {
47 654 : m_cmdline->set_author(get_author());
48 654 : m_cmdline->set_committer(get_committer());
49 654 : m_cmdline->set_id(get_id());
50 654 : m_cmdline->set_debug(get_debug());
51 654 : m_cmdline->set_progress_callback(get_progress_callback());
52 :
53 654 : m_libgit2->set_author(get_author());
54 654 : m_libgit2->set_committer(get_committer());
55 654 : m_libgit2->set_id(get_id());
56 654 : m_libgit2->set_debug(get_debug());
57 654 : m_libgit2->set_progress_callback(get_progress_callback());
58 654 : }
59 :
60 198 : Result Git::refresh_libgit2()
61 : {
62 198 : if (m_folder.empty()) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "no repository folder");
63 :
64 198 : const auto start = std::chrono::steady_clock::now();
65 :
66 198 : if (m_libgit2->is_open())
67 : {
68 51 : auto result = m_libgit2->close();
69 51 : if (result.error()) return ESYSREPO_RESULT(result);
70 51 : }
71 198 : auto result = m_libgit2->open(m_folder);
72 :
73 396 : m_last_refresh_ms = static_cast<uint64_t>(
74 198 : std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count());
75 :
76 198 : return result;
77 198 : }
78 :
79 223 : uint64_t Git::take_last_refresh_ms()
80 : {
81 223 : const uint64_t ms = m_last_refresh_ms;
82 223 : m_last_refresh_ms = 0;
83 223 : return ms;
84 : }
85 :
86 121 : Result Git::open(const std::string &folder)
87 : {
88 121 : sync_meta();
89 :
90 121 : auto result = m_cmdline->open(folder);
91 121 : if (result.error()) return ESYSREPO_RESULT(result);
92 :
93 121 : result = m_libgit2->open(folder);
94 121 : if (result.error())
95 : {
96 0 : m_cmdline->close();
97 0 : return ESYSREPO_RESULT(result);
98 : }
99 :
100 121 : m_folder = folder;
101 121 : open_time();
102 121 : return ESYSREPO_RESULT(ResultCode::OK);
103 121 : }
104 :
105 491 : bool Git::is_open()
106 : {
107 491 : return m_cmdline->is_open() && m_libgit2->is_open();
108 : }
109 :
110 269 : Result Git::close()
111 : {
112 538 : Result first = ESYSREPO_RESULT(ResultCode::OK);
113 :
114 269 : if (m_libgit2->is_open())
115 : {
116 269 : auto result = m_libgit2->close();
117 269 : if (result.error() && first.ok()) first = result;
118 269 : }
119 269 : if (m_cmdline->is_open())
120 : {
121 269 : auto result = m_cmdline->close();
122 269 : if (result.error() && first.ok()) first = result;
123 269 : }
124 :
125 269 : m_folder.clear();
126 269 : close_time();
127 807 : return first.ok() ? ESYSREPO_RESULT(ResultCode::OK) : ESYSREPO_RESULT(first);
128 269 : }
129 :
130 0 : void Git::close_on_error()
131 : {
132 0 : if (is_open() || m_cmdline->is_open() || m_libgit2->is_open()) close();
133 0 : }
134 :
135 177 : Result Git::clone(const std::string &url, const std::string &path, const std::string &rev,
136 : const git::CloneOptions &options)
137 : {
138 177 : sync_meta();
139 :
140 177 : auto result = m_cmdline->clone(url, path, rev, options);
141 207 : if (result.error()) return ESYSREPO_RESULT(result);
142 :
143 147 : m_folder = path;
144 147 : result = refresh_libgit2();
145 147 : if (result.error())
146 : {
147 0 : m_cmdline->close();
148 0 : m_folder.clear();
149 0 : return ESYSREPO_RESULT(result);
150 : }
151 :
152 147 : open_time();
153 147 : return ESYSREPO_RESULT(ResultCode::OK);
154 177 : }
155 :
156 50 : Result Git::fetch(const std::string &remote)
157 : {
158 50 : sync_meta();
159 :
160 50 : auto result = m_cmdline->fetch(remote);
161 51 : if (result.error()) return ESYSREPO_RESULT(result);
162 :
163 : // External fetch updates on-disk refs; reopen libgit2 so later ops see them.
164 49 : return refresh_libgit2();
165 50 : }
166 :
167 2 : Result Git::fetch_refspec(const std::string &refspec, const std::string &remote)
168 : {
169 2 : sync_meta();
170 :
171 2 : auto result = m_cmdline->fetch_refspec(refspec, remote);
172 2 : if (result.error()) return ESYSREPO_RESULT(result);
173 :
174 2 : return refresh_libgit2();
175 2 : }
176 :
177 0 : Result Git::update_submodules(const std::string &path, bool recursive)
178 : {
179 0 : sync_meta();
180 0 : return m_cmdline->update_submodules(path, recursive);
181 : }
182 :
183 1 : Result Git::worktree_add_detach(const std::string &path, const std::string &commit)
184 : {
185 1 : sync_meta();
186 1 : return m_cmdline->worktree_add_detach(path, commit);
187 : }
188 :
189 0 : Result Git::worktree_list(std::vector<git::Worktree> &worktrees)
190 : {
191 0 : sync_meta();
192 0 : return m_cmdline->worktree_list(worktrees);
193 : }
194 :
195 0 : Result Git::worktree_remove(const std::string &path, bool force)
196 : {
197 0 : sync_meta();
198 0 : return m_cmdline->worktree_remove(path, force);
199 : }
200 :
201 1 : Result_t<bool> Git::has_object(const std::string &commit)
202 : {
203 1 : sync_meta();
204 1 : return m_cmdline->has_object(commit);
205 : }
206 :
207 1 : Result Git::list_gitlinks(std::map<std::string, std::string> &out, const std::string &rev)
208 : {
209 1 : sync_meta();
210 1 : return m_cmdline->list_gitlinks(out, rev);
211 : }
212 :
213 1 : Result Git::list_index_gitlinks(std::map<std::string, std::string> &out)
214 : {
215 1 : sync_meta();
216 1 : return m_cmdline->list_index_gitlinks(out);
217 : }
218 :
219 0 : Result Git::init_bare(const std::string &folder_path)
220 : {
221 0 : sync_meta();
222 :
223 0 : auto result = m_libgit2->init_bare(folder_path);
224 0 : if (result.error()) return ESYSREPO_RESULT(result);
225 :
226 0 : m_folder = folder_path;
227 0 : result = m_cmdline->open(folder_path);
228 0 : if (result.error())
229 : {
230 0 : m_libgit2->close();
231 0 : m_folder.clear();
232 0 : return ESYSREPO_RESULT(result);
233 : }
234 :
235 0 : open_time();
236 0 : return ESYSREPO_RESULT(ResultCode::OK);
237 0 : }
238 :
239 1 : Result Git::init(const std::string &folder_path)
240 : {
241 1 : sync_meta();
242 :
243 1 : auto result = m_libgit2->init(folder_path);
244 1 : if (result.error()) return ESYSREPO_RESULT(result);
245 :
246 1 : m_folder = folder_path;
247 1 : result = m_cmdline->open(folder_path);
248 1 : if (result.error())
249 : {
250 0 : m_libgit2->close();
251 0 : m_folder.clear();
252 0 : return ESYSREPO_RESULT(result);
253 : }
254 :
255 1 : open_time();
256 1 : return ESYSREPO_RESULT(ResultCode::OK);
257 1 : }
258 :
259 2 : Result Git::get_remotes(std::vector<git::Remote> &remotes)
260 : {
261 2 : sync_meta();
262 2 : return m_libgit2->get_remotes(remotes);
263 : }
264 :
265 16 : Result Git::get_head_branch_remote(git::Branch &branch, git::Remote &remote)
266 : {
267 16 : sync_meta();
268 16 : return m_libgit2->get_head_branch_remote(branch, remote);
269 : }
270 :
271 1 : Result Git::add_remote(const std::string &name, const std::string &url)
272 : {
273 1 : sync_meta();
274 1 : return m_libgit2->add_remote(name, url);
275 : }
276 :
277 47 : Result Git::get_branches(git::Branches &branches, git::BranchType branch_type)
278 : {
279 47 : sync_meta();
280 47 : return m_libgit2->get_branches(branches, branch_type);
281 : }
282 :
283 52 : Result Git::checkout(const std::string &branch, bool force)
284 : {
285 52 : sync_meta();
286 52 : return m_libgit2->checkout(branch, force);
287 : }
288 :
289 0 : Result Git::reset(const git::CommitHash &commit, git::ResetType type)
290 : {
291 0 : sync_meta();
292 0 : return m_libgit2->reset(commit, type);
293 : }
294 :
295 1 : Result Git::fastforward(const git::CommitHash &commit)
296 : {
297 1 : sync_meta();
298 1 : return m_libgit2->fastforward(commit);
299 : }
300 :
301 22 : Result Git::get_last_commit(git::CommitHash &commit)
302 : {
303 22 : sync_meta();
304 22 : return m_libgit2->get_last_commit(commit);
305 : }
306 :
307 9 : Result Git::get_last_commit(git::Commit &commit, bool get_all_notes)
308 : {
309 9 : sync_meta();
310 9 : return m_libgit2->get_last_commit(commit, get_all_notes);
311 : }
312 :
313 0 : Result Git::get_parent_commit(const git::CommitHash &commit, git::CommitHash &parent, int nth_parent)
314 : {
315 0 : sync_meta();
316 0 : return m_libgit2->get_parent_commit(commit, parent, nth_parent);
317 : }
318 :
319 15 : Result Git::is_dirty(bool &dirty)
320 : {
321 15 : sync_meta();
322 15 : return m_libgit2->is_dirty(dirty);
323 : }
324 :
325 33 : Result Git::is_detached(bool &detached)
326 : {
327 33 : sync_meta();
328 33 : return m_libgit2->is_detached(detached);
329 : }
330 :
331 20 : Result Git::get_status(git::RepoStatus &status)
332 : {
333 20 : sync_meta();
334 20 : return m_libgit2->get_status(status);
335 : }
336 :
337 2 : Result_t<bool> Git::is_ssh_agent_running(bool log_once)
338 : {
339 2 : sync_meta();
340 2 : return m_libgit2->is_ssh_agent_running(log_once);
341 : }
342 :
343 2 : void Git::detect_ssh_agent(bool log_once)
344 : {
345 2 : sync_meta();
346 2 : m_libgit2->detect_ssh_agent(log_once);
347 2 : }
348 :
349 7 : Result Git::merge_analysis(const std::vector<std::string> &refs, git::MergeAnalysisResult &merge_analysis_result,
350 : std::vector<git::CommitHash> &commits)
351 : {
352 7 : sync_meta();
353 7 : return m_libgit2->merge_analysis(refs, merge_analysis_result, commits);
354 : }
355 :
356 1 : Result Git::fetch_all_notes(const std::string &remote)
357 : {
358 1 : sync_meta();
359 1 : return m_libgit2->fetch_all_notes(remote);
360 : }
361 :
362 26 : Result_t<bool> Git::has_branch(const std::string &name, git::BranchType branch_type)
363 : {
364 26 : sync_meta();
365 26 : return m_libgit2->has_branch(name, branch_type);
366 : }
367 :
368 21 : Result Git::get_hash(const std::string &revision, std::string &hash, git::BranchType branch_type)
369 : {
370 21 : sync_meta();
371 21 : return m_libgit2->get_hash(revision, hash, branch_type);
372 : }
373 :
374 0 : Result Git::walk_commits(std::shared_ptr<git::WalkCommit> walk_commit)
375 : {
376 0 : sync_meta();
377 0 : return m_libgit2->walk_commits(walk_commit);
378 : }
379 :
380 0 : Result Git::diff(const git::CommitHash &commit_hash, std::shared_ptr<git::Diff> diff)
381 : {
382 0 : sync_meta();
383 0 : return m_libgit2->diff(commit_hash, diff);
384 : }
385 :
386 0 : Result Git::get_ahead_behind(git::AheadBehind &ahead_behind, const git::Branch &local_branch)
387 : {
388 0 : sync_meta();
389 0 : return m_libgit2->get_ahead_behind(ahead_behind, local_branch);
390 : }
391 :
392 20 : Result Git::get_ahead_behind(git::AheadBehind &ahead_behind, const std::string &first_ref, const std::string &second_ref)
393 : {
394 20 : sync_meta();
395 20 : return m_libgit2->get_ahead_behind(ahead_behind, first_ref, second_ref);
396 : }
397 :
398 2 : Result Git::add_note(git::NoteId ¬e_id, const std::string &reference, const std::string &message, bool overwrite)
399 : {
400 2 : sync_meta();
401 2 : return m_libgit2->add_note(note_id, reference, message, overwrite);
402 : }
403 :
404 0 : Result Git::remove_note(const git::NoteId ¬e_id)
405 : {
406 0 : sync_meta();
407 0 : return m_libgit2->remove_note(note_id);
408 : }
409 :
410 0 : Result Git::remove_note(const git::CommitHash &commit_hash, const std::string &reference)
411 : {
412 0 : sync_meta();
413 0 : return m_libgit2->remove_note(commit_hash, reference);
414 : }
415 :
416 0 : Result Git::remove_note_last_commit(const std::string &reference)
417 : {
418 0 : sync_meta();
419 0 : return m_libgit2->remove_note_last_commit(reference);
420 : }
421 :
422 0 : Result Git::push(const std::string &remote, const std::string &src_ref, const std::string &dst_ref)
423 : {
424 0 : sync_meta();
425 0 : return m_libgit2->push(remote, src_ref, dst_ref);
426 : }
427 :
428 0 : Result Git::push_notes(const std::string &remote, const std::string &reference)
429 : {
430 0 : sync_meta();
431 0 : return m_libgit2->push_notes(remote, reference);
432 : }
433 :
434 0 : const std::string &Git::get_version()
435 : {
436 0 : return s_get_version();
437 : }
438 :
439 0 : const std::string &Git::get_lib_name()
440 : {
441 0 : return s_get_lib_name();
442 : }
443 :
444 0 : const std::string &Git::s_get_version()
445 : {
446 0 : static const std::string version = "0.1.0";
447 0 : return version;
448 : }
449 :
450 0 : const std::string &Git::s_get_lib_name()
451 : {
452 0 : static const std::string name = "hybrid";
453 0 : return name;
454 : }
455 :
456 420 : void Git::set_logger_if(std::shared_ptr<log::Logger_if> logger_if)
457 : {
458 840 : log::User::set_logger_if(logger_if);
459 840 : m_cmdline->set_logger_if(logger_if);
460 840 : m_libgit2->set_logger_if(logger_if);
461 420 : }
462 :
463 3 : bool Git::do_is_ssh_backend_supported(git::SshBackend backend) const
464 : {
465 3 : return m_libgit2->do_is_ssh_backend_supported(backend);
466 : }
467 :
468 2 : bool Git::do_is_ssh_backend_available(git::SshBackend backend, bool force)
469 : {
470 2 : return m_libgit2->do_is_ssh_backend_available(backend, force);
471 : }
472 :
473 5 : Result Git::do_set_ssh_backend(git::SshBackend backend)
474 : {
475 5 : return m_libgit2->do_set_ssh_backend(backend);
476 : }
477 :
478 65 : Result_t<git::SshBackend> Git::do_get_ssh_backend() const
479 : {
480 65 : return m_libgit2->do_get_ssh_backend();
481 : }
482 :
483 : } // namespace esys::repo::hybrid
|