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