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