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 183 : Git::Git()
29 183 : : GitBase()
30 : {
31 183 : m_cmdline = std::make_shared<gitcmdline::Git>();
32 183 : m_libgit2 = std::make_shared<libgit2::Git>();
33 183 : }
34 :
35 183 : Git::~Git()
36 : {
37 183 : if (is_open()) close();
38 549 : }
39 :
40 181 : std::shared_ptr<GitBase> Git::new_ptr()
41 : {
42 181 : return std::make_shared<Git>();
43 : }
44 :
45 646 : void Git::sync_meta()
46 : {
47 646 : m_cmdline->set_author(get_author());
48 646 : m_cmdline->set_committer(get_committer());
49 646 : m_cmdline->set_id(get_id());
50 646 : m_cmdline->set_debug(get_debug());
51 646 : m_cmdline->set_progress_callback(get_progress_callback());
52 :
53 646 : m_libgit2->set_author(get_author());
54 646 : m_libgit2->set_committer(get_committer());
55 646 : m_libgit2->set_id(get_id());
56 646 : m_libgit2->set_debug(get_debug());
57 646 : m_libgit2->set_progress_callback(get_progress_callback());
58 646 : }
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 119 : Result Git::open(const std::string &folder)
87 : {
88 119 : sync_meta();
89 :
90 119 : auto result = m_cmdline->open(folder);
91 119 : if (result.error()) return ESYSREPO_RESULT(result);
92 :
93 119 : result = m_libgit2->open(folder);
94 119 : if (result.error())
95 : {
96 0 : m_cmdline->close();
97 0 : return ESYSREPO_RESULT(result);
98 : }
99 :
100 119 : m_folder = folder;
101 119 : open_time();
102 119 : return ESYSREPO_RESULT(ResultCode::OK);
103 119 : }
104 :
105 488 : bool Git::is_open()
106 : {
107 488 : return m_cmdline->is_open() && m_libgit2->is_open();
108 : }
109 :
110 267 : Result Git::close()
111 : {
112 534 : Result first = ESYSREPO_RESULT(ResultCode::OK);
113 :
114 267 : if (m_libgit2->is_open())
115 : {
116 267 : auto result = m_libgit2->close();
117 267 : if (result.error() && first.ok()) first = result;
118 267 : }
119 267 : if (m_cmdline->is_open())
120 : {
121 267 : auto result = m_cmdline->close();
122 267 : if (result.error() && first.ok()) first = result;
123 267 : }
124 :
125 267 : m_folder.clear();
126 267 : close_time();
127 801 : return first.ok() ? ESYSREPO_RESULT(ResultCode::OK) : ESYSREPO_RESULT(first);
128 267 : }
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 0 : Result Git::init_bare(const std::string &folder_path)
184 : {
185 0 : sync_meta();
186 :
187 0 : auto result = m_libgit2->init_bare(folder_path);
188 0 : if (result.error()) return ESYSREPO_RESULT(result);
189 :
190 0 : m_folder = folder_path;
191 0 : result = m_cmdline->open(folder_path);
192 0 : if (result.error())
193 : {
194 0 : m_libgit2->close();
195 0 : m_folder.clear();
196 0 : return ESYSREPO_RESULT(result);
197 : }
198 :
199 0 : open_time();
200 0 : return ESYSREPO_RESULT(ResultCode::OK);
201 0 : }
202 :
203 1 : Result Git::init(const std::string &folder_path)
204 : {
205 1 : sync_meta();
206 :
207 1 : auto result = m_libgit2->init(folder_path);
208 1 : if (result.error()) return ESYSREPO_RESULT(result);
209 :
210 1 : m_folder = folder_path;
211 1 : result = m_cmdline->open(folder_path);
212 1 : if (result.error())
213 : {
214 0 : m_libgit2->close();
215 0 : m_folder.clear();
216 0 : return ESYSREPO_RESULT(result);
217 : }
218 :
219 1 : open_time();
220 1 : return ESYSREPO_RESULT(ResultCode::OK);
221 1 : }
222 :
223 1 : Result Git::get_remotes(std::vector<git::Remote> &remotes)
224 : {
225 1 : sync_meta();
226 1 : return m_libgit2->get_remotes(remotes);
227 : }
228 :
229 16 : Result Git::get_head_branch_remote(git::Branch &branch, git::Remote &remote)
230 : {
231 16 : sync_meta();
232 16 : return m_libgit2->get_head_branch_remote(branch, remote);
233 : }
234 :
235 1 : Result Git::add_remote(const std::string &name, const std::string &url)
236 : {
237 1 : sync_meta();
238 1 : return m_libgit2->add_remote(name, url);
239 : }
240 :
241 47 : Result Git::get_branches(git::Branches &branches, git::BranchType branch_type)
242 : {
243 47 : sync_meta();
244 47 : return m_libgit2->get_branches(branches, branch_type);
245 : }
246 :
247 52 : Result Git::checkout(const std::string &branch, bool force)
248 : {
249 52 : sync_meta();
250 52 : return m_libgit2->checkout(branch, force);
251 : }
252 :
253 0 : Result Git::reset(const git::CommitHash &commit, git::ResetType type)
254 : {
255 0 : sync_meta();
256 0 : return m_libgit2->reset(commit, type);
257 : }
258 :
259 1 : Result Git::fastforward(const git::CommitHash &commit)
260 : {
261 1 : sync_meta();
262 1 : return m_libgit2->fastforward(commit);
263 : }
264 :
265 21 : Result Git::get_last_commit(git::CommitHash &commit)
266 : {
267 21 : sync_meta();
268 21 : return m_libgit2->get_last_commit(commit);
269 : }
270 :
271 9 : Result Git::get_last_commit(git::Commit &commit, bool get_all_notes)
272 : {
273 9 : sync_meta();
274 9 : return m_libgit2->get_last_commit(commit, get_all_notes);
275 : }
276 :
277 0 : Result Git::get_parent_commit(const git::CommitHash &commit, git::CommitHash &parent, int nth_parent)
278 : {
279 0 : sync_meta();
280 0 : return m_libgit2->get_parent_commit(commit, parent, nth_parent);
281 : }
282 :
283 15 : Result Git::is_dirty(bool &dirty)
284 : {
285 15 : sync_meta();
286 15 : return m_libgit2->is_dirty(dirty);
287 : }
288 :
289 33 : Result Git::is_detached(bool &detached)
290 : {
291 33 : sync_meta();
292 33 : return m_libgit2->is_detached(detached);
293 : }
294 :
295 20 : Result Git::get_status(git::RepoStatus &status)
296 : {
297 20 : sync_meta();
298 20 : return m_libgit2->get_status(status);
299 : }
300 :
301 2 : Result_t<bool> Git::is_ssh_agent_running(bool log_once)
302 : {
303 2 : sync_meta();
304 2 : return m_libgit2->is_ssh_agent_running(log_once);
305 : }
306 :
307 2 : void Git::detect_ssh_agent(bool log_once)
308 : {
309 2 : sync_meta();
310 2 : m_libgit2->detect_ssh_agent(log_once);
311 2 : }
312 :
313 7 : Result Git::merge_analysis(const std::vector<std::string> &refs, git::MergeAnalysisResult &merge_analysis_result,
314 : std::vector<git::CommitHash> &commits)
315 : {
316 7 : sync_meta();
317 7 : return m_libgit2->merge_analysis(refs, merge_analysis_result, commits);
318 : }
319 :
320 1 : Result Git::fetch_all_notes(const std::string &remote)
321 : {
322 1 : sync_meta();
323 1 : return m_libgit2->fetch_all_notes(remote);
324 : }
325 :
326 26 : Result_t<bool> Git::has_branch(const std::string &name, git::BranchType branch_type)
327 : {
328 26 : sync_meta();
329 26 : return m_libgit2->has_branch(name, branch_type);
330 : }
331 :
332 21 : Result Git::get_hash(const std::string &revision, std::string &hash, git::BranchType branch_type)
333 : {
334 21 : sync_meta();
335 21 : return m_libgit2->get_hash(revision, hash, branch_type);
336 : }
337 :
338 0 : Result Git::walk_commits(std::shared_ptr<git::WalkCommit> walk_commit)
339 : {
340 0 : sync_meta();
341 0 : return m_libgit2->walk_commits(walk_commit);
342 : }
343 :
344 0 : Result Git::diff(const git::CommitHash &commit_hash, std::shared_ptr<git::Diff> diff)
345 : {
346 0 : sync_meta();
347 0 : return m_libgit2->diff(commit_hash, diff);
348 : }
349 :
350 0 : Result Git::get_ahead_behind(git::AheadBehind &ahead_behind, const git::Branch &local_branch)
351 : {
352 0 : sync_meta();
353 0 : return m_libgit2->get_ahead_behind(ahead_behind, local_branch);
354 : }
355 :
356 20 : Result Git::get_ahead_behind(git::AheadBehind &ahead_behind, const std::string &first_ref, const std::string &second_ref)
357 : {
358 20 : sync_meta();
359 20 : return m_libgit2->get_ahead_behind(ahead_behind, first_ref, second_ref);
360 : }
361 :
362 2 : Result Git::add_note(git::NoteId ¬e_id, const std::string &reference, const std::string &message, bool overwrite)
363 : {
364 2 : sync_meta();
365 2 : return m_libgit2->add_note(note_id, reference, message, overwrite);
366 : }
367 :
368 0 : Result Git::remove_note(const git::NoteId ¬e_id)
369 : {
370 0 : sync_meta();
371 0 : return m_libgit2->remove_note(note_id);
372 : }
373 :
374 0 : Result Git::remove_note(const git::CommitHash &commit_hash, const std::string &reference)
375 : {
376 0 : sync_meta();
377 0 : return m_libgit2->remove_note(commit_hash, reference);
378 : }
379 :
380 0 : Result Git::remove_note_last_commit(const std::string &reference)
381 : {
382 0 : sync_meta();
383 0 : return m_libgit2->remove_note_last_commit(reference);
384 : }
385 :
386 0 : Result Git::push(const std::string &remote, const std::string &src_ref, const std::string &dst_ref)
387 : {
388 0 : sync_meta();
389 0 : return m_libgit2->push(remote, src_ref, dst_ref);
390 : }
391 :
392 0 : Result Git::push_notes(const std::string &remote, const std::string &reference)
393 : {
394 0 : sync_meta();
395 0 : return m_libgit2->push_notes(remote, reference);
396 : }
397 :
398 0 : const std::string &Git::get_version()
399 : {
400 0 : return s_get_version();
401 : }
402 :
403 0 : const std::string &Git::get_lib_name()
404 : {
405 0 : return s_get_lib_name();
406 : }
407 :
408 0 : const std::string &Git::s_get_version()
409 : {
410 0 : static const std::string version = "0.1.0";
411 0 : return version;
412 : }
413 :
414 0 : const std::string &Git::s_get_lib_name()
415 : {
416 0 : static const std::string name = "hybrid";
417 0 : return name;
418 : }
419 :
420 420 : void Git::set_logger_if(std::shared_ptr<log::Logger_if> logger_if)
421 : {
422 840 : log::User::set_logger_if(logger_if);
423 840 : m_cmdline->set_logger_if(logger_if);
424 840 : m_libgit2->set_logger_if(logger_if);
425 420 : }
426 :
427 3 : bool Git::do_is_ssh_backend_supported(git::SshBackend backend) const
428 : {
429 3 : return m_libgit2->do_is_ssh_backend_supported(backend);
430 : }
431 :
432 2 : bool Git::do_is_ssh_backend_available(git::SshBackend backend, bool force)
433 : {
434 2 : return m_libgit2->do_is_ssh_backend_available(backend, force);
435 : }
436 :
437 5 : Result Git::do_set_ssh_backend(git::SshBackend backend)
438 : {
439 5 : return m_libgit2->do_set_ssh_backend(backend);
440 : }
441 :
442 67 : Result_t<git::SshBackend> Git::do_get_ssh_backend() const
443 : {
444 67 : return m_libgit2->do_get_ssh_backend();
445 : }
446 :
447 : } // namespace esys::repo::hybrid
|