Line data Source code
1 : /*!
2 : * \file esys/repo/libgit2/git_libgit2.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/libgit2/git.h"
20 : #include "esys/repo/libgit2/gitimpl.h"
21 :
22 : #include <git2.h>
23 :
24 : #include <boost/filesystem.hpp>
25 : #include <boost/process/search_path.hpp>
26 :
27 : #include <cstdlib>
28 : #include <string>
29 : #include <string_view>
30 :
31 : namespace esys::repo::libgit2
32 : {
33 :
34 : bool Git::s_detect_ssh_agent_done = false;
35 : bool Git::s_ssh_agent_running = false;
36 : bool Git::s_exec_probe_done = false;
37 : bool Git::s_exec_available = false;
38 : int Git::s_libssh2_supported = -1;
39 : int Git::s_exec_supported = -1;
40 :
41 0 : std::shared_ptr<GitBase> Git::new_ptr()
42 : {
43 0 : return std::make_shared<Git>();
44 : }
45 :
46 260 : Git::Git()
47 260 : : GitBase()
48 : {
49 260 : m_impl = std::make_unique<GitImpl>(this);
50 260 : }
51 :
52 260 : Git::~Git() = default;
53 :
54 389 : Result Git::open(const std::string &folder)
55 : {
56 389 : set_folder(folder);
57 389 : return get_impl()->open(folder);
58 : }
59 :
60 835 : bool Git::is_open()
61 : {
62 835 : return get_impl()->is_open();
63 : }
64 :
65 427 : Result Git::close()
66 : {
67 427 : return get_impl()->close();
68 : }
69 :
70 3 : Result Git::init_bare(const std::string &folder_path)
71 : {
72 3 : return get_impl()->init_bare(folder_path);
73 : }
74 :
75 1 : Result Git::init(const std::string &folder_path)
76 : {
77 1 : return get_impl()->init(folder_path);
78 : }
79 :
80 0 : void Git::close_on_error()
81 : {
82 0 : return get_impl()->close_on_error();
83 : }
84 :
85 4 : Result Git::get_remotes(std::vector<git::Remote> &remotes)
86 : {
87 4 : return get_impl()->get_remotes(remotes);
88 : }
89 :
90 17 : Result Git::get_head_branch_remote(git::Branch &branch, git::Remote &remote)
91 : {
92 17 : return get_impl()->get_head_branch_remote(branch, remote);
93 : }
94 :
95 6 : Result Git::add_remote(const std::string &name, const std::string &url)
96 : {
97 6 : return get_impl()->add_remote(name, url);
98 : }
99 :
100 94 : Result Git::get_branches(git::Branches &branches, git::BranchType branch_type)
101 : {
102 94 : return get_impl()->get_branches(branches, branch_type);
103 : }
104 :
105 37 : Result Git::clone(const std::string &url, const std::string &path, const std::string &rev,
106 : const git::CloneOptions &options)
107 : {
108 37 : set_url(url);
109 37 : return get_impl()->clone(url, path, rev, options);
110 : }
111 :
112 62 : Result Git::checkout(const std::string &branch, bool force)
113 : {
114 62 : return get_impl()->checkout(branch, force);
115 : }
116 :
117 2 : Result Git::reset(const git::CommitHash &commit, git::ResetType type)
118 : {
119 2 : return get_impl()->reset(commit, type);
120 : }
121 :
122 2 : Result Git::fastforward(const git::CommitHash &commit)
123 : {
124 2 : return get_impl()->fastforward(commit);
125 : }
126 :
127 31 : Result Git::get_last_commit(git::CommitHash &commit)
128 : {
129 31 : return get_impl()->get_last_commit(commit);
130 : }
131 :
132 19 : Result Git::get_last_commit(git::Commit &commit, bool get_all_notes)
133 : {
134 19 : return get_impl()->get_last_commit(commit, get_all_notes);
135 : }
136 :
137 5 : Result Git::get_parent_commit(const git::CommitHash &commit, git::CommitHash &parent, int nth_parent)
138 : {
139 5 : return get_impl()->get_parent_commit(commit, parent, nth_parent);
140 : }
141 :
142 42 : Result Git::is_dirty(bool &dirty)
143 : {
144 42 : return get_impl()->is_dirty(dirty);
145 : }
146 :
147 54 : Result Git::is_detached(bool &detached)
148 : {
149 54 : return get_impl()->is_detached(detached);
150 : }
151 :
152 31 : Result Git::get_status(git::RepoStatus &repo_status)
153 : {
154 31 : return m_impl->get_status(repo_status);
155 : }
156 :
157 25 : Result_t<bool> Git::is_ssh_agent_running(bool log_once)
158 : {
159 25 : if (!s_detect_ssh_agent_done) detect_ssh_agent(log_once);
160 :
161 25 : return s_ssh_agent_running;
162 : }
163 :
164 26 : void Git::detect_ssh_agent(bool log_once)
165 : {
166 26 : if (s_detect_ssh_agent_done) return;
167 :
168 1 : s_detect_ssh_agent_done = true;
169 1 : s_ssh_agent_running = m_impl->is_ssh_agent_running();
170 :
171 2 : if (s_ssh_agent_running && log_once) info("SSH agent detected");
172 : }
173 :
174 11 : bool Git::do_is_ssh_backend_supported(git::SshBackend backend) const
175 : {
176 11 : if (!(git_libgit2_features() & GIT_FEATURE_SSH)) return false;
177 :
178 11 : switch (backend)
179 : {
180 7 : case git::SshBackend::LibSSH2: return probe_ssh_backend_compiled("libssh2", s_libssh2_supported);
181 4 : case git::SshBackend::Exec: return probe_ssh_backend_compiled("exec", s_exec_supported);
182 : }
183 : return false;
184 : }
185 :
186 11 : bool Git::probe_ssh_backend_compiled(const char *name, int &cache)
187 : {
188 11 : if (cache >= 0) return cache != 0;
189 :
190 2 : git_buf previous = GIT_BUF_INIT;
191 2 : const bool had_previous = (git_libgit2_opts(GIT_OPT_GET_SSH_BACKEND, &previous) == 0);
192 :
193 2 : const int set_error = git_libgit2_opts(GIT_OPT_SET_SSH_BACKEND, name);
194 2 : cache = (set_error == 0) ? 1 : 0;
195 :
196 2 : if (had_previous && previous.ptr != nullptr)
197 2 : git_libgit2_opts(GIT_OPT_SET_SSH_BACKEND, previous.ptr);
198 0 : else if (set_error == 0)
199 : // Do not leave a temporary probe selection as the process-wide backend
200 : // when there was no prior GET value to restore.
201 0 : git_libgit2_opts(GIT_OPT_SET_SSH_BACKEND, "libssh2");
202 :
203 2 : git_buf_dispose(&previous);
204 2 : return cache != 0;
205 : }
206 :
207 : namespace
208 : {
209 :
210 2 : std::string get_env_var(const char *name)
211 : {
212 : #ifdef _WIN32
213 : char *buffer = nullptr;
214 : size_t length = 0;
215 : if (_dupenv_s(&buffer, &length, name) != 0 || buffer == nullptr) return {};
216 : std::string value(buffer);
217 : free(buffer);
218 : return value;
219 : #else
220 2 : const char *value = std::getenv(name);
221 2 : return value != nullptr ? std::string(value) : std::string();
222 : #endif
223 : }
224 :
225 : } // namespace
226 :
227 1 : bool Git::probe_openssh_client()
228 : {
229 2 : auto command_exists = [](const std::string &command) -> bool {
230 1 : if (command.empty()) return false;
231 :
232 1 : boost::filesystem::path path(command);
233 2 : if (path.is_absolute() || path.has_parent_path()) return boost::filesystem::exists(path);
234 :
235 : #ifdef _WIN32
236 : boost::filesystem::path found = boost::process::search_path(path.filename().string());
237 : #else
238 2 : boost::filesystem::path found = boost::process::search_path(command);
239 : #endif
240 1 : return !found.empty();
241 2 : };
242 :
243 1 : const std::string ssh_command = get_env_var("GIT_SSH_COMMAND");
244 1 : if (!ssh_command.empty())
245 : {
246 : // Shell command line: verify the first token looks findable when possible.
247 0 : auto start = ssh_command.find_first_not_of(" \t");
248 0 : if (start == std::string::npos) return true;
249 0 : auto end = ssh_command.find_first_of(" \t", start);
250 0 : const std::string first_token =
251 0 : ssh_command.substr(start, end == std::string::npos ? std::string::npos : end - start);
252 0 : if (first_token.empty()) return true;
253 0 : return command_exists(first_token);
254 0 : }
255 :
256 1 : const std::string ssh = get_env_var("GIT_SSH");
257 1 : if (!ssh.empty()) return command_exists(ssh);
258 :
259 : #ifdef _WIN32
260 : return command_exists("ssh.exe") || command_exists("ssh");
261 : #else
262 1 : return command_exists("ssh");
263 : #endif
264 1 : }
265 :
266 3 : bool Git::do_is_ssh_backend_available(git::SshBackend backend, bool force)
267 : {
268 3 : if (!do_is_ssh_backend_supported(backend)) return false;
269 :
270 3 : if (backend == git::SshBackend::LibSSH2) return true;
271 :
272 2 : if (force) s_exec_probe_done = false;
273 :
274 2 : if (!s_exec_probe_done)
275 : {
276 1 : s_exec_probe_done = true;
277 1 : s_exec_available = probe_openssh_client();
278 : }
279 :
280 2 : return s_exec_available;
281 : }
282 :
283 5 : Result Git::do_set_ssh_backend(git::SshBackend backend)
284 : {
285 5 : if (!do_is_ssh_backend_supported(backend))
286 0 : return ESYSREPO_RESULT(ResultCode::GIT_SSH_BACKEND_NOT_SUPPORTED);
287 :
288 5 : if (backend == git::SshBackend::Exec && !do_is_ssh_backend_available(backend, false))
289 0 : return ESYSREPO_RESULT(ResultCode::GIT_SSH_BACKEND_NOT_SUPPORTED, "OpenSSH client not found");
290 :
291 5 : const char *name = (backend == git::SshBackend::Exec) ? "exec" : "libssh2";
292 5 : int error = git_libgit2_opts(GIT_OPT_SET_SSH_BACKEND, name);
293 5 : if (error < 0) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, error);
294 :
295 5 : return ESYSREPO_RESULT(ResultCode::OK);
296 : }
297 :
298 67 : Result_t<git::SshBackend> Git::do_get_ssh_backend() const
299 : {
300 67 : if (!(git_libgit2_features() & GIT_FEATURE_SSH))
301 0 : return ESYSREPO_RESULT_T(ResultCode::GIT_SSH_BACKEND_NOT_SUPPORTED, git::SshBackend::LibSSH2);
302 :
303 67 : git_buf buf = GIT_BUF_INIT;
304 67 : int error = git_libgit2_opts(GIT_OPT_GET_SSH_BACKEND, &buf);
305 67 : if (error < 0)
306 0 : return ESYSREPO_RESULT_T(ResultCode::GIT_GENERIC_ERROR, git::SshBackend::LibSSH2);
307 :
308 67 : git::SshBackend backend = git::SshBackend::LibSSH2;
309 67 : if (buf.ptr != nullptr && std::string_view(buf.ptr) == "exec") backend = git::SshBackend::Exec;
310 :
311 67 : git_buf_dispose(&buf);
312 134 : return backend;
313 : }
314 :
315 27 : Result Git::merge_analysis(const std::vector<std::string> &refs, git::MergeAnalysisResult &merge_analysis_result,
316 : std::vector<git::CommitHash> &commits)
317 : {
318 27 : return m_impl->merge_analysis(refs, merge_analysis_result, commits);
319 : }
320 :
321 36 : Result Git::fetch(const std::string &remote)
322 : {
323 36 : return m_impl->fetch(remote);
324 : }
325 :
326 0 : Result Git::fetch_refspec(const std::string &refspec, const std::string &remote)
327 : {
328 0 : return m_impl->fetch_refspec(refspec, remote);
329 : }
330 :
331 0 : Result Git::update_submodules(const std::string &path, bool recursive)
332 : {
333 0 : return m_impl->update_submodules(path, recursive);
334 : }
335 :
336 3 : Result Git::fetch_all_notes(const std::string &remote)
337 : {
338 3 : return m_impl->fetch_all_notes(remote);
339 : }
340 :
341 5 : Result Git::add_note(git::NoteId ¬e_id, const std::string &reference, const std::string &message, bool overwrite)
342 : {
343 5 : return m_impl->add_note(note_id, reference, message, overwrite);
344 : }
345 :
346 1 : Result Git::remove_note(const git::NoteId ¬e_id)
347 : {
348 1 : return m_impl->remove_note(note_id);
349 : }
350 :
351 0 : Result Git::remove_note(const git::CommitHash &commit_hash, const std::string &reference)
352 : {
353 0 : return m_impl->remove_note(commit_hash, reference);
354 : }
355 :
356 1 : Result Git::remove_note_last_commit(const std::string &reference)
357 : {
358 1 : return m_impl->remove_note_last_commit(reference);
359 : }
360 :
361 2 : Result Git::push(const std::string &remote, const std::string &src_ref, const std::string &dst_ref)
362 : {
363 2 : return m_impl->push(remote, src_ref, dst_ref);
364 : }
365 :
366 1 : Result Git::push_notes(const std::string &remote, const std::string &reference)
367 : {
368 1 : return m_impl->push_notes(remote, reference);
369 : }
370 :
371 33 : Result_t<bool> Git::has_branch(const std::string &name, git::BranchType branch_type)
372 : {
373 33 : return m_impl->has_branch(name, branch_type);
374 : }
375 :
376 27 : Result Git::get_hash(const std::string &revision, std::string &hash, git::BranchType branch_type)
377 : {
378 27 : return m_impl->get_hash(revision, hash, branch_type);
379 : }
380 :
381 2 : Result Git::walk_commits(std::shared_ptr<git::WalkCommit> walk_commit)
382 : {
383 6 : return m_impl->walk_commits(walk_commit);
384 : }
385 :
386 4 : Result Git::diff(const git::CommitHash &commit_hash, std::shared_ptr<git::Diff> diff)
387 : {
388 12 : return m_impl->diff(commit_hash, diff);
389 : }
390 :
391 0 : Result Git::get_ahead_behind(git::AheadBehind &ahead_behind, const git::Branch &local_branch)
392 : {
393 0 : return m_impl->get_ahead_behind(ahead_behind, local_branch);
394 : }
395 :
396 20 : Result Git::get_ahead_behind(git::AheadBehind &ahead_behind, const std::string &first_ref,
397 : const std::string &second_ref)
398 : {
399 20 : return m_impl->get_ahead_behind(ahead_behind, first_ref, second_ref);
400 : }
401 :
402 37 : void Git::set_url(const std::string &url)
403 : {
404 37 : m_url = url;
405 37 : }
406 :
407 0 : const std::string &Git::get_url()
408 : {
409 0 : return m_url;
410 : }
411 :
412 389 : void Git::set_folder(const std::string &folder)
413 : {
414 389 : m_folder = folder;
415 389 : }
416 :
417 0 : const std::string &Git::get_folder()
418 : {
419 0 : return m_folder;
420 : }
421 :
422 0 : const std::string &Git::get_version()
423 : {
424 0 : return m_impl->get_version();
425 : }
426 :
427 0 : const std::string &Git::get_lib_name()
428 : {
429 0 : return m_impl->get_lib_name();
430 : }
431 :
432 164 : void Git::debug(int level, const std::string &msg)
433 : {
434 : // log::ConsoleLockGuard<log::User> lock(this);
435 : //! \TODO must be fixed to allow writing after all console is written
436 :
437 164 : GitBase::debug(level, msg);
438 164 : }
439 :
440 2 : const std::string &Git::s_get_version()
441 : {
442 2 : return GitImpl::s_get_version();
443 : }
444 :
445 2 : const std::string &Git::s_get_lib_name()
446 : {
447 2 : return GitImpl::s_get_lib_name();
448 : }
449 :
450 2 : const std::string &Git::s_get_ssh_version()
451 : {
452 2 : return GitImpl::s_get_ssh_version();
453 : }
454 :
455 2 : const std::string &Git::s_get_ssh_lib_name()
456 : {
457 2 : return GitImpl::s_get_ssh_lib_name();
458 : }
459 :
460 2603 : GitImpl *Git::get_impl() const
461 : {
462 2603 : return m_impl.get();
463 : }
464 :
465 542 : void Git::set_logger_if(std::shared_ptr<log::Logger_if> logger_if)
466 : {
467 1084 : get_impl()->set_logger_if(logger_if);
468 542 : }
469 :
470 : } // namespace esys::repo::libgit2
|